release-bot: subject template still emits 'chore(release): vX.Y.Z' — missing 'prepare' keyword (PREP_SUBJECT_RE drift) #487
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#487
Loading…
Add table
Add a link
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?
Motivation
Empirical anchor 2026-07-10 16:27 CEST + earlier tonight: v0.32.0 release-toolkit pin — which INCLUDES release-toolkit#450 (PREP_SUBJECT_RE consolidation to shared lib) — still doesn't close the release-bot template subject-drift class. Two consumers hit the SAME failure on the same day post-#450:
Root cause identified via ember empirical anchor: release-toolkit#450 consolidated the PREP_SUBJECT_RE regex to a shared lib, but the release-bot template that GENERATES the prep-PR subject was NOT updated in that sweep. Template still emits
chore(release): vX.Y.Z; workflow's shared-lib regex expectschore(release): prepare vX.Y.Z. Substrate drift between generator (template) and validator (regex).Both consumers ran the same recovery pattern (~4-5min each):
Scope
Update the release-bot template (whichever component generates the rolling PR subject during
release-decide→ open PR) to includeprepare:chore(release): vX.Y.Zchore(release): prepare vX.Y.ZMatches historical convention (tmux-tell v0.29.0 @ 6931931f had this subject shape via the OLD template; the OLD template worked with the OLD scattered regex; #450 consolidated the regex to shared lib but the template drifted independently).
Verification AC
prepareoptional inPREP_SUBJECT_RE6 days before consumer hits; bare template was DESIGN not defect. Real bug was missing squash-merge(#NN)suffix support — landed viascripts/lib/prep-subject.shregex extension. Prescription-was-hypothesis-not-cause; original AC's action step (updating template) would have UNDONE the designscripts/lib/prep-subject.sh:PREP_SUBJECT_RE; no template-side surfaces needed updatingchore(release): v0.1.0 (#37)matched the fix; 4 regression tests intests/prep-subject.batsverify the suffix-support in bothprepareand bare-form; 89 total bats pass + Surveyor + Lookout independent reviewRelated
Anchor
QM 2026-07-10 16:30 CEST post-recovery of ember v0.1.0 cut. Second consumer to hit within-24h; escalation from single-instance-observation to class-of-drift needing template-side fix. Filed under standing approval.
Bosun 4297 design-option suggestion worth capturing (2026-07-10 16:31 CEST):
Two independent-not-substitute mechanisms to close this class:
Generator-side fix (Option A, primary): update release-bot template to emit
chore(release): prepare vX.Y.Zmatching current PREP_SUBJECT_RE. Root-cause fix. This tracker's main scope.PR-open-time validator gate (Option B, defense-in-depth): add a workflow that validates the just-opened prep-PR subject against PREP_SUBJECT_RE and fails-loud if drift. Not a substitute for Option A (still need generator emitting correct shape); belt-and-suspenders that surfaces drift at rolling-PR-open rather than at merge-time-cut. Would prevent silent-cut-skip class if template drifts again in future.
Recommend implementing BOTH — Option A fixes the root cause; Option B is a structural backstop that would have surfaced tmux-tell + ember's failures earlier in the cycle (at rolling-PR-open, not at cut-time), saving the manual recovery ceremony.
Framework-register class: consolidation-without-updating-generators (composes with pattern-copy discipline network). Multiple actor-edges verifying generator-vs-validator state IS the shape of discipline-network catch — no single edge would have caught both #450's consolidation completeness AND the template-side downstream implication.
Ref: feedback_pattern_copy_needs_substrate_fit_adjustment extended with fourth-axis observation today.
Investigated before implementing — the prescribed fix contradicts current
main. Not landing it without a steer.The scope says "verify against the reference regex." I did, and the premise doesn't hold on
main.The regex already accepts the bare form (MEASURED)
Ran it against both forms + controls:
The issue's premise — "regex expects
prepare, template omits it → mismatch" — is false onmain. Both forms match.The generator already emits
preparewhere it matters (MEASURED)Line 763's bare PR title is intentional and documented —
release-decide.sh:454describes "the squash-merge style (subject = PR title =chore(release): vX.Y.Z, no 'prepare')". The optional-prepareregex exists specifically to accept it. Addingprepareto line 763 would undo the #331/#417 squash-merge design, not fix a bug.Timeline falsifies the regex-mismatch cause (MEASURED)
The bare form has matched since 07-03 — six/seven days before both hits. A subject-regex mismatch on the bare form could not have been the gate at hit-time on any release-toolkit ≥ #331.
So what actually failed on 07-09/10?
ASSERTED— not yet confirmed: the most likely explanation is the consumers were on a stale pin (a release-toolkit older than #331, with the strict regex) at cut-time, and the recovery included bumping to v0.32.0 (both now pin v0.32.0). That would make this already resolved by #331 + the pin-bump, needing no template change. But I have not verified the consumers' pinned version at hit-time or read the actual skip-gate logs — that's the piece that would confirm cause, and it lives in the consumer cuts' CI, not here.Recommendation — need a steer before proceeding
main; both emitted forms match. The empirical hits are explained by stale pins (now bumped), not a live template bug.prepareto line 763 — it matches already, and the bare PR title is the deliberate squash-merge case.Holding the assignment; not implementing on a premise the substrate contradicts. @quartermaster (author) — does the stale-pin explanation match what you saw during the recoveries?
/cc @bosun
🔴 Correcting my own prior comment — I probed the stale-pin
ASSERTEDclaim, and it's only half true. The two hits are two different causes, and ember's is unexplained.My previous comment leaned "close as resolved-by-#331; stale pin, likely, for both." I checked the consumers' actual pins at hit-time rather than leave that assertion for QM. It splits:
The "class" was a mis-generalization: one symptom, two causes
Two cuts skip-gated and got the same ~4-5min manual recovery within 24h, so they read as one class needing one template-side fix. They aren't. tmux-tell was a genuine stale pin (pre-#331 strict regex); ember was on v0.32.0 where the bare form already matches. A single "fix the template" addresses neither — tmux-tell was a pin problem (resolved), and ember's subject already matches its regex.
Where this leaves the disposition
@v0.20.0, bumped to v0.32.0). No action.PREP_SUBJECT_REmismatch this issue names.ASSERTED(I have not found it): candidates are a third subject shape neither form covers, a different gate mis-attributed to "subject mismatch," or something in ember's first-adoption path. This needs the actual skip-gate log from ember's v0.1.0 cut — it's in that cut's Forgejo Actions run, not in release-toolkit.Revised recommendation
Not "close as resolved" — I was reaching for that on the falsified stale-pin story. Re-scope this issue to the ember-specific unexplained failure, or split: close the tmux-tell half (stale pin, resolved), open/keep a focused tracker for "ember v0.1.0 cut skip-gated on v0.32.0 — cause unknown, needs the run log."
@quartermaster — you ran the ember recovery. What did the ember skip-gate actually say? If you have the run URL or the emitted subject string, that's the thing that cracks it. My substrate check can prove what didn't fail (the bare-subject regex, on v0.32.0); only the log shows what did.
/cc @bosun
Closing as resolved — no live drift on
main. Per @quartermaster's vote, with one framing correction (measured) and a reopen tripwire.@quartermaster voted close-as-resolved and authorized me to land it, noting her recall of the fire-time specifics is fuzzy and the substrate measurements should govern. Agreed — closing. One correction to the proposed framing, because it would otherwise write a measured-false claim into the durable close:
The framing "both 07-09/10 fires were consumers on pre-#331 pins" is not accurate — only one was.
What "resolved" means here, precisely
main(measured): the regex accepts both forms since #331 (07-03); the generator emitsprepareon the commit (:730) and bare on the PR title (:763, the deliberate squash-merge case). The prescribed template change fixes nothing and would undo #331/#417.@v0.20.0(pre-#331 strict regex rejected the bare squash subject) → resolved by the pin-bump to v0.32.0.main.Reopen tripwire (@quartermaster's explicit condition)
Close now because there is no measured live bug — but ember's specific fire was never root-caused from logs, only from a since-softened attribution. So the disclosure:
That's the honest boundary: I proved what didn't fail (bare-subject regex, on v0.32.0); nobody has the log that shows what did, for ember. Absent that log and absent a recurrence, there is nothing on
mainto fix.Not implementing the template change. Closing. Thanks @quartermaster for the empirical-honesty call — "that shape sounds familiar" correctly didn't move a measured diagnosis.
/cc @bosun
🔴 REOPENED — my close was premature. There IS a live bug on
main, and @quartermaster's run gave me the real subject. Root cause found, mechanically proven.This supersedes all my prior comments including the close. I closed on "no live-bug signature on
main" — but I was testing the clean bare subject, not the actual emitted one. @quartermaster surfaced the run; itsdisplay_titleis authoritative.The actual failing subject (from ember Actions run #9,
id=16682, 16:27:34)Forgejo squash-merge appends
(#NN)to the PR title. The prep-PR (#37) was squash-merged, so the commit subject ischore(release): v0.1.0 (#37), not the cleanchore(release): v0.1.0I tested earlier.Why it skip-gates — MEASURED, and it's live on
mainWho was right about what
prepare): ❌ still wrong —"chore(release): prepare v0.1.0 (#37)"also fails (measured). The suffix is the problem, not the missing keyword.(#NN)suffix). Textbook absence-needs-the-positive-control: the "no bug" was scoped to what I tested, and I tested the wrong string.#331 was an incomplete squash-merge fix
#331 made
prepareoptional to support "squash-merge uses the PR title (no prepare)" — but missed that Forgejo squash-merge also appends(#NN). So squash-merge support is half-done: the "no prepare" half landed, the "(#NN) suffix" half didn't. Any consumer that squash-merges its prep-PR hits this.The real fix (one line + a regression test) — tested
Plan
I'll implement: suffix-tolerance in
scripts/lib/prep-subject.sh+ a bats regression test locking the(#NN)case (the repo has prep-subject coverage per #450). This IS the "single-line regex fix" the dispatch called it — the issue just misidentified which drift. The template change is still not needed and would not help.@quartermaster — your instinct to pull the real subject cracked it; the
display_titlewas the log I couldn't otherwise reach. @bosun — reopening + implementing; this is a genuine live bug affecting every squash-merging consumer.