rt prep --rolling-mode creates a duplicate rolling PR: FindPRByHead sends head=owner:branch, which the forge now honours and which matches nothing #1584
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit#1584
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 prep --rolling-modehas been failing onfrankenbit/storeysince 2026-10-21T19:15Z — three runs, all fatal, after every piece of real work had already succeeded.The cut is in fact prepared:
release-prep/rollingis atb758d7cf, PR #189 carries it,mergeable=true. Only the PR step failed, and it failed because it tried to create a PR that has existed since 2026-10-16.The cause: a filter the code calls unreliable, sent in a form that cannot match
internal/forgejo/reads.go:486(unchanged since11025e9, 2026-07-26, and identical atv0.66.0andmain):Its own doc comment says "its server-side
headfilter is unreliable (#274), so the match is client-side on head.label OR head.ref exactly". The client-side match is sound. It is being starved of input. Measured againstfrankenbit/storeyon Forgejo 17.0.0:The forge honours the filter, and
owner:branchmatches no head, so the list comes back empty, the client-side matcher has nothing to walk,FindPRByHeadreturnsErrNotFound,CheckRollingPRreportsFound=false, andOpenOrUpdatePRtakes the create branch —POST /pulls→ 409.🔑 The defect is relying on a parameter being IGNORED.
#274established the filter was unreliable; the response was to keep sending it and match client-side. That works for exactly as long as the server discards it. It is a latent create-a-duplicate waiting for the day the server starts honouring it, and the failure mode is not a wrong answer — it is a mutating call.⚠️ And
head.labeldoes not carry the owner prefix on this instance — PR #189 readshead.label=release-prep/rolling, same ashead.ref. So thelabelhalf of the client-side match is also built on an assumption that does not hold here; it only works becausehead.refis checked too.What I did NOT establish
That 17.0.0 is what began honouring it.
routers/api/v1/repo/pull.gois byte-identical atv16.0.6andv17.0.0for both the list filter (HeadBranch: ctx.FormTrim("head")) and the duplicate check (StatusConflictfromGetUnmergedPullRequest) — so the change, if there was one, is below the handler in the issues model, which I did not compare. The circumstantial case is that all three failures follow the 17.0.0 upgrade at 2026-10-21T15:42Z and the 2026-10-20T00:34Z run reached the same point and emitted its outputs without a POST error.⚠️ Three hypotheses died on the way here and are recorded so nobody re-runs them: the forge's list-filter handler changed (identical source) ·
POST /pullsused to be idempotent (both tags return 409 from identical code, and the live forge returns 409) · the rt version changed (rt-v0.66.0-linux-amd64in both runs' cache keys). And two instruments failed: astrings | grepover the forge binaries returned clean zeros for a positive control that must be present, and job-log mtimes are not run times — a log whose mtime reads 2026-10-21T12:59 contains a run from 2026-10-20T00:34, which invalidated a 128-invocation timeline I had built from them.A direct A/B is available if anyone wants the attribution:
rollback-pre-17.0.0/is a 16.0.6-readable snapshot taken at 2026-10-21T17:42 local containing PR #189, so booting 16.0.6 against a copy and asking it the same two queries would settle it in one run.The fixes, in order
headat all — a parameter that is not sent cannot be honoured wrongly. Sendinghead=<branch>without the owner prefix also works today, but it keeps the dependency.OpenOrUpdatePRidempotent on 409. A conflict fromCreatePRmeans the PR exists, so re-find andUpdatePRinstead of failing. The find→create window is real even with (1) fixed.head.labelassumption, or document that only thehead.refarm is load-bearing on Forgejo.Filed from alcatraz. Consequence beyond storey: every repo whose rolling cut runs through
--rolling-modewill take the create branch on every refresh once its rolling PR exists, so this is not storey-specific.Addressed in PR #1585 (head
9ab8282), awaiting review.Item (4) of this issue is withdrawn — I filed it and it was wrong. I claimed the
head.labelarm of the client-side match rests on an assumption that does not hold here, because PR #189 readshead.label=release-prep/rollingwith no owner prefix. The matcher compares both arms against the bare branch name, so both work; I had conflated the query string'sowner:prefix with the comparison. AGENTS.md §head.ref-replaced-on-delete is why the label arm exists:head.refis replaced withrefs/pull/N/headonce the branch is deleted, which broke the Layer 2 cut-safeguard in v0.6.0 and was fixed in v0.6.1 by readinghead.label. Removing it would reintroduce that, so the PR adds an arm pinning it instead.Item (3) is answered rather than implemented. With the 409 handled, the observed fail-after-mutation case is gone, and making the remaining PR-step failures non-fatal would be swallowing them — the exit status should stay non-zero when something genuinely fails. What was wrong was legibility, not control flow.
Fixed by PR #1585, merged fast-forward-only at
b4758310on review 8010 (@surveyor,official=true,stale=false,commit_id=b4758310), all 5 required contexts green by complement.Items (1) and (2) implemented; (3) answered — the reorder is not available, because commit, push and PR-open are fixed in that order and the body is composed from the sealed CHANGELOG, and it would not have prevented this anyway since the find ran before the create and returned a wrong answer rather than failing; (4) withdrawn as my own error, with an arm added to pin what it would have broken.
Storey's rolling cut should refresh cleanly on its next push to main.
release: published, and stop reopening a closed tracker #1587