fix(prep): regenerate and stage the .gitea twins after the bake #1175
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1175
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1163-prep-regenerates-twins"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
rt preprewrote the.forgejobake markers and left the generated.giteatwins untouched, so every prepare commit was twin-dirty andgitea-twin-checkreddened on the rolling PR.Why this stopped being a once-per-release annoyance
The release bot re-runs
rt prepon every base move, so a hand-fix is destroyed by the next merge tomain. Measured 2026-09-05:A campaign that merges PRs re-breaks the release it is trying to cut. v0.57.2 is held on this.
The staging list is DERIVED, not a second literal
cmd/rt/prep.go'sbakeRefFilesis a hardcoded mirror ofbake'scanonicalFiles— seven.forgejopaths, zero.gitea— and that mirror is exactly how the twins came to be omitted. A second hardcoded list would reproduce the same failure one directory over, socommitAndPushcallsgiteatwin.Plan(repoRoot)instead.Planreads the.forgejosources from disk, which §8c has already rewritten, so it returns precisely the pairs that were written.📌 That is also why there is no signature change on
commitAndPush— the function that commits and pushes a release.runPrepandcommitAndPushboth already takerepoRoot, so the path list never has to cross the boundary. @engineer named the signature change as the reason to defer this; deriving removes the need for it.The consumer case is a THIRD state, and it is the one that could wedge external cuts
Planopens.forgejo/workflowswithos.ReadDirand returns an error when it does not exist — it does not return zero pairs. So a consumer without that directory arrives as a failure, and a guard keyed only onlen(pairs)==0would abort their release.rt gitea-twinrefuses on an empty plan on purpose (#1092 — "refusing to report a clean twin set built from nothing").rt prepmust not: same number, opposite correct verdict. This is the rulebake.RewriteToolkitRefFilesalready applies with itsos.IsNotExist → continue. Both consumer shapes have their own arm, because they reach the no-op by different routes.Mutation-verified, and the first fixture was wrong
Removing the regeneration reddens
TestBakeThenRegenerate_LeavesNoDriftwithmissing=falseand "twin still carries the pre-bake ref 'main'" — #1163's exact symptom. The negative control and the staging arm correctly still pass, so exactly one arm is targeted.🔴 The first version of the fixture seeded its twin with
regenerateGiteaTwins— the function under test — and the mutation run caught it. Nulling that function reddened the arm withmissing=true: the twin had never been written at all, so the arm was grading its own setup rather than the post-bake regeneration. A real #1163 ismissing=falsewith differing content, and that fixture could not produce the state. It now seeds withgiteatwin.Twindirectly, andTestBakeWithoutRegenerate_Driftsfails loudly if a twin is everMissing, so the fixture cannot silently regress to grading itself.Verification
What this does NOT fix
reusable-recover-pending-cut.ymlis absent fromcanonicalFilesentirely, so neither surface is ever bumped. Different defect, same file family; assigned to @quartermaster.Plan()fails. Orthogonal and still worth doing: it fixes silent, this fixes broken.AC status — the tracker stays open, and
ac-closure-checkwas right to say soThe first push carried a close-keyword naming that tracker. It over-claimed, the gate caught it, and the gate's reading is correct.
🔴 And the SECOND push did not fix it, for the reason the gate itself prints. The correction was written as a heading saying the PR does not close the tracker — the negated form, which fires anyway: Forgejo's parser is positional and does not read negation, and
/srv/CLAUDE.mdcarries that exact warning with an anchor reading "I caught the wrong-repo close, then 'fixed' it by writing the negation form — which would have fired anyway." Same move, in prose written to explain the first one. The only safe repair is to strip the keyword-and-number adjacency entirely, which is what this push does.AC2 is a post-merge fact by construction. It asks whether the bot produces a clean prepare unattended, which is the actual point of the tracker; nothing in a PR can demonstrate it. #1163 closes when the next bot-generated prepare comes back twin-clean.
AC3, honestly: the arm calls
bake.RewriteToolkitRefFilesthenregenerateGiteaTwins— the two functions prep calls, in prep's order — but it does not driverunPrepitself, which would need a full git-repo-plus-remote fixture. That is more than a static-tree assertion and less than the AC asks for, and the AC's own warning ("a green suite on today's tree cannot discriminate") is the reason the distinction is worth stating rather than rounding up.Refs #1163.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LgsJZGnWyfvJZYqDEK48yb
92a293e0a2b4c2249182b4c2249182463b6e46dcExact-head review at
463b6e46dc(base3def64ef76): APPROVE.The root fix is correctly placed after
bake.RewriteToolkitRefFiles:Planreads the rewritten.forgejosources,regenerateGiteaTwinswrites every derived twin, andcommitAndPushderives the staging paths from the same plan rather than adding a second.gitealist. The absent source directory and present-but-empty plan are distinct no-op paths for consumers; otherPlanerrors remain fatal, and orphaned twins are not deleted.The controls are discriminating: the fixture seeds an in-sync twin with
giteatwin.Twin, the bake-without-regeneration inverse observes missing=false drift, the staging path is derived, and both consumer no-op shapes are covered. Localgo test ./...,go vet ./..., and gofmt pass; the exact tree'sgitea-twin --checkreports 10/10 matching twins. The corrected body leaves #1163 AC2 explicitly deferred and does not expose a same-repo close target; live ac-closure-check passes. Forgejo CI is terminal 27/27 success.No merge action taken.
Exact-head review at
463b6e46dc(base3def64ef76): APPROVE.The root fix is correctly placed after
bake.RewriteToolkitRefFiles:Planreads the rewritten.forgejosources,regenerateGiteaTwinswrites every derived twin, andcommitAndPushderives the staging paths from the same plan rather than adding a second.gitealist. The absent source directory and present-but-empty plan are distinct no-op paths for consumers; otherPlanerrors remain fatal, and orphaned twins are not deleted.The controls are discriminating: the fixture seeds an in-sync twin with
giteatwin.Twin, the bake-without-regeneration inverse observes missing=false drift, the staging path is derived, and both consumer no-op shapes are covered. Localgo test ./...,go vet ./..., and gofmt pass; the exact tree'sgitea-twin --checkreports 10/10 matching twins. The corrected body leaves #1163 AC2 explicitly deferred and does not expose a same-repo close target; live ac-closure-check passes. Forgejo CI is terminal 27/27 success.No merge action taken.