bug(repin): rt repin bakes the rc tag from an unfetched local main, and AGENTS.md section 2.5 names a REPIN_REVIEWER env var it never reads #1566
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit#1566
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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 repinbuilds its rc tag from whatever the operator's localmainholds. It never runsgit fetch. The retiredscripts/repin.shfetched first and built fromorigin/main, and AGENTS.md §2.5 still describes the script. The same section also names a$REPIN_REVIEWERenv var thatrt repindoes not read.Measured
All reads are on live
origin/mainc2fad81, 2026-10-06.1. No fetch, and the base defaults to the local branch.
cmd/rt/repin.go:194defaults--basetomain, the local branch.bakeCommitresolves it withgit rev-parse --verify <base>(:215).grep -n fetch cmd/rt/repin.gofinds nothing.git show 25c2fb3:scripts/repin.sh, lines 86-90) rangit fetch "$REMOTE"and defaulted the base to${REMOTE}/main. Its comment says "The rc must be cut from current remote main."origin/main".2. The re-pin branch starts at the operator's HEAD, not at the base. Found while reading the script beside the port.
commitInWorktreerunsgit worktree add -q -b repin/<tag> <tmp>with no start point (cmd/rt/repin.go:280). The branch starts at whatever the operator has checked out.git worktree add -q --detach "$TMPWT" "$BASE_SHA"(line 157).--basemoves the bake but not the branch.3.
$REPIN_REVIEWERis documented but not read.$REPIN_REVIEWERwhen it is set.grep -rn REPIN_REVIEWER cmd internalis empty.rt repintakes a repeatable--reviewerflag (cmd/rt/repin.go:195).REPIN_REMOTEis--remote, andREPIN_BASEis--base.4. A comment misstates the script's fallback.
cmd/rt/repin.go:340-343says "repin.sh falls back to the --base flag" when the forge cannot name the default branch. The script fell back to the literalmain(lines 193-195). The two agree today only because--basedefaults tomain.Reproduction against real git. The setup is a bare remote, an operator clone, and one more commit pushed to the remote's
mainthat the clone has not fetched.runRepinran with the command's own--basedefault and a faked forge. An in-test bake recorded the commit it was handed. Results onc2fad81:--basedefault (main)--base origin/main, clone fetched firstThe second row isolates finding 2. Even a correct base leaves the branch on the operator's HEAD.
Why it matters
The rc re-pin is dormant for toolkit-self (AGENTS.md §2.2, #456). It exists for the day someone pins toolkit-self to a tag on purpose. That day comes after a long dormancy, when a local
mainis most likely to be stale.On that day,
rt repinwould push an rc tag built from old code. The tag push is the first irreversible step, so the rc number is spent before anyone can look.Nothing reliably catches it afterwards. This is read from AGENTS.md, not run on the forge:
build-ref-checkgrades whether the tag carries its own name in the marker. A stale tag does, so it passes.check-self-bootstrapcompares only the guarded compose files (§2.3). It goes red onpush: mainonly if one of those changed between the stale base and main. A Go-only difference turns nothing red (§2.2).This is the carry-forward loss AGENTS.md §1 describes. The fetch was load-bearing setup in the script, and the port dropped it without saying so. §2.5's dry-run caveat calls itself "the one behavioural difference the port did not preserve". This issue adds three more.
Acceptance criteria
rt repinfetches--remotebefore it resolves the base. If the fetch fails, it refuses before any tag, branch or PR exists.--base, the bake is built from<remote>/mainas just fetched, not from the localmain.repin/<rc-tag>branch starts at the same base commit as the bake.main, not toorigin/main.rt repin --dry-runprints the base ref and commit it would build from.c2fad81. Each new guard is mutation-verified, and the PR body shows the runs.--reviewerinstead of$REPIN_REVIEWER. The dry-run caveat no longer calls itself the only port difference.go test ./...andgolangci-lintpass.Out of scope
realBakeitself. That is #1563.rt repin --dry-runbuild the bake, as the script's dry run did. §2.5 already discloses that difference.main. The script also hard-codedmain, andcli-surface.mdlistsrepinas toolkit-self only, whose default branch ismain.Already fixed and left open — verified against
origin/mainf87c0d3Timestamp corrected: this comment first read "2026-10-19 00:40". The reads were taken then, but the comment posted at 08:44 and I stamped it from a stale clock.
origin/mainis stillf87c0d3at 08:44, so every row below holds — which is the argument for anchoring on the SHA rather than the hour.b246ab5(PR #1567, merged 2026-10-06T15:59) landed all four findings. Re-derived here per ACrather than taken from the PR body, since the question is whether main holds the behaviour now:
f87c0d3resolveBase,cmd/rt/repin.go:217—git fetch --quiet <remote>, error returns before anything is written--basebuilds from<remote>/main:198flag default"", documented and resolved as<remote>/mainrepin/<rc-tag>branch starts at the bake's base:345git worktree add -q -b <branch> <tmp> <baseSHA>, with:342"Start the branch at the base the bake was built from, as repin.sh did"main, notorigin/main:410-416—strings.TrimPrefix(baseRef, o.remote+"/"), with the reason recorded: a PR cannot targetorigin/main, and refusing would let a transient forge outage block a break-glass--dry-runprints the base ref and commit:383[repin] base %s = %s— emitted before the dry-run return at:401, so the dry run gets itcmd/rt/repin_base_test.go—newStaleRepobuilds a bare remote plus an unfetched clone;TestRunRepin_BuildsFromTheFetchedRemoteMain,TestRunRepin_RefusesWhenTheFetchFails,TestRunRepin_PlansTheBasesWrappers,TestRunRepin_UngradeableBaseWrapperRefusesBeforeTheTagrt repinreads none of$REPIN_REVIEWER/$REPIN_REMOTE/$REPIN_BASEand takes flags insteadgo test ./...andgolangci-lintf87c0d3:go test ./...rc=0, all packages ok;golangci-lint run ./...0 issues, rc=0Finding 4 — the comment claiming "repin.sh falls back to the --base flag" — is gone with the
rewrite;
:412-415now states the script's actual fallback and why the port keeps it.Closing. Nothing was left to implement; the ticket outlived its fix by thirteen days.