release-bot: subject template still emits 'chore(release): vX.Y.Z' — missing 'prepare' keyword (PREP_SUBJECT_RE drift) #487

Closed
opened 2026-07-10 16:30:28 +02:00 by quartermaster · 5 comments

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:

  1. tmux-tell v0.30.0 cut (2026-07-09 22:57 CEST) → subject-mismatch skip-gate → manual recovery (tmux-tell#737)
  2. ember v0.1.0 cut (2026-07-10 16:27 CEST) → SAME subject-mismatch skip-gate → SAME manual recovery pattern

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 expects chore(release): prepare vX.Y.Z. Substrate drift between generator (template) and validator (regex).

Both consumers ran the same recovery pattern (~4-5min each):

  • Tag manually at prep-PR-merge SHA
  • Create Forgejo release via API with CHANGELOG-sourced body
  • Direct-push manifest bookkeeping commit per ADR-0007 path α

Scope

Update the release-bot template (whichever component generates the rolling PR subject during release-decide → open PR) to include prepare:

  • Before: chore(release): vX.Y.Z
  • After: chore(release): prepare vX.Y.Z

Matches 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

  • Original prescription REFUTED via substrate check (Shipwright PR#495): #331/#417 made prepare optional in PREP_SUBJECT_RE 6 days before consumer hits; bare template was DESIGN not defect. Real bug was missing squash-merge (#NN) suffix support — landed via scripts/lib/prep-subject.sh regex extension. Prescription-was-hypothesis-not-cause; original AC's action step (updating template) would have UNDONE the design
  • Cross-check MOOT — the prescription that motivated this AC was wrong-mechanism (AC1 above). The actual defect (regex missing suffix support) is a single-source fix in scripts/lib/prep-subject.sh:PREP_SUBJECT_RE; no template-side surfaces needed updating
  • Consumer smoke test satisfied empirically: Shipwright pulled ember#37 CI log showing failing subject chore(release): v0.1.0 (#37) matched the fix; 4 regression tests in tests/prep-subject.bats verify the suffix-support in both prepare and bare-form; 89 total bats pass + Surveyor + Lookout independent review
  • Docs already reflected correct shape post-#331 (prepare optional); PR#495's regex extension is a superset of prior valid subjects — no consumer-facing doc drift introduced
  • release-toolkit#450 (PREP_SUBJECT_RE regex consolidation — the FIX that missed updating the template that emits matches to that regex)
  • tmux-tell#737 (sibling consumer-side tracker for this same drift)
  • tmux-tell#732 (2026-07-09 empirical hit)
  • ember#28 (adoption arc that hit this same drift on first cut)

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.

## 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: 1. **tmux-tell v0.30.0 cut** (2026-07-09 22:57 CEST) → subject-mismatch skip-gate → manual recovery (tmux-tell#737) 2. **ember v0.1.0 cut** (2026-07-10 16:27 CEST) → SAME subject-mismatch skip-gate → SAME manual recovery pattern **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 expects `chore(release): prepare vX.Y.Z`. Substrate drift between generator (template) and validator (regex). **Both consumers ran the same recovery pattern** (~4-5min each): - Tag manually at prep-PR-merge SHA - Create Forgejo release via API with CHANGELOG-sourced body - Direct-push manifest bookkeeping commit per ADR-0007 path α ## Scope Update the release-bot template (whichever component generates the rolling PR subject during `release-decide` → open PR) to include `prepare`: - Before: `chore(release): vX.Y.Z` - After: `chore(release): prepare vX.Y.Z` Matches 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 - [x] Original prescription REFUTED via substrate check (Shipwright PR#495): #331/#417 made `prepare` optional in `PREP_SUBJECT_RE` 6 days before consumer hits; bare template was DESIGN not defect. Real bug was missing squash-merge `(#NN)` suffix support — landed via `scripts/lib/prep-subject.sh` regex extension. Prescription-was-hypothesis-not-cause; original AC's action step (updating template) would have UNDONE the design - [x] Cross-check MOOT — the prescription that motivated this AC was wrong-mechanism (AC1 above). The actual defect (regex missing suffix support) is a single-source fix in `scripts/lib/prep-subject.sh:PREP_SUBJECT_RE`; no template-side surfaces needed updating - [x] Consumer smoke test satisfied empirically: Shipwright pulled ember#37 CI log showing failing subject `chore(release): v0.1.0 (#37)` matched the fix; 4 regression tests in `tests/prep-subject.bats` verify the suffix-support in both `prepare` and bare-form; 89 total bats pass + Surveyor + Lookout independent review - [x] Docs already reflected correct shape post-#331 (prepare optional); PR#495's regex extension is a superset of prior valid subjects — no consumer-facing doc drift introduced ## Related - release-toolkit#450 (PREP_SUBJECT_RE regex consolidation — the FIX that missed updating the template that emits matches to that regex) - tmux-tell#737 (sibling consumer-side tracker for this same drift) - tmux-tell#732 (2026-07-09 empirical hit) - ember#28 (adoption arc that hit this same drift on first cut) ## 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.
Author
Owner

Bosun 4297 design-option suggestion worth capturing (2026-07-10 16:31 CEST):

"If template-fix doesn't land before another release cycle, worth thinking about a workflow-check that validates prep-subject against PREP_SUBJECT_RE at PR-open time (would fail-loud the drift instead of cutting-time recovery)."

Two independent-not-substitute mechanisms to close this class:

  1. Generator-side fix (Option A, primary): update release-bot template to emit chore(release): prepare vX.Y.Z matching current PREP_SUBJECT_RE. Root-cause fix. This tracker's main scope.

  2. 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.

**Bosun 4297 design-option suggestion worth capturing** (2026-07-10 16:31 CEST): > "If template-fix doesn't land before another release cycle, worth thinking about a workflow-check that validates prep-subject against PREP_SUBJECT_RE at PR-open time (would fail-loud the drift instead of cutting-time recovery)." **Two independent-not-substitute mechanisms** to close this class: 1. **Generator-side fix (Option A, primary)**: update release-bot template to emit `chore(release): prepare vX.Y.Z` matching current PREP_SUBJECT_RE. Root-cause fix. This tracker's main scope. 2. **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.
Owner

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)

scripts/lib/prep-subject.sh:
PREP_SUBJECT_RE='^chore\(release\):[[:space:]]+(prepare[[:space:]]+)?v?([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)$'
                                              ^^^^^^^^^^^^^^^^^^^^^^^ OPTIONAL

Ran it against both forms + controls:

MEASURED  "chore(release): v0.1.0"          → ✅ MATCH   ← the form the issue says is rejected
MEASURED  "chore(release): prepare v0.1.0"  → ✅ MATCH
MEASURED  "chore(foo): v1.0.0"              → 🔴 no match  (negative control — regex discriminates)

The issue's premise — "regex expects prepare, template omits it → mismatch" — is false on main. Both forms match.

The generator already emits prepare where it matters (MEASURED)

release-prep.sh:730   git commit -m "chore(release): prepare ${NEW_TAG}"   ← COMMIT: has "prepare"
release-prep.sh:763   PR_TITLE="chore(release): ${NEW_TAG}"                 ← PR TITLE: bare, BY DESIGN

Line 763's bare PR title is intentional and documentedrelease-decide.sh:454 describes "the squash-merge style (subject = PR title = chore(release): vX.Y.Z, no 'prepare')". The optional-prepare regex exists specifically to accept it. Adding prepare to line 763 would undo the #331/#417 squash-merge design, not fix a bug.

Timeline falsifies the regex-mismatch cause (MEASURED)

2026-07-03  #331 landed — regex made prepare OPTIONAL (bare form accepted)
2026-07-07  v0.32.0 cut (includes #450 → includes #331)
2026-07-09  tmux-tell v0.30.0 hit
2026-07-10  ember v0.1.0 hit + this issue filed

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

  1. Most likely: close as resolved-by-#331 — no generator/validator drift exists on main; both emitted forms match. The empirical hits are explained by stale pins (now bumped), not a live template bug.
  2. If there's a real unexplained failure, point me at the actual skip-gate log from the tmux-tell/ember cut and I'll chase the true cause — but it isn't the bare-subject regex.
  3. The one thing I would NOT do: add prepare to 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

## 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) ``` scripts/lib/prep-subject.sh: PREP_SUBJECT_RE='^chore\(release\):[[:space:]]+(prepare[[:space:]]+)?v?([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)$' ^^^^^^^^^^^^^^^^^^^^^^^ OPTIONAL ``` Ran it against both forms + controls: ``` MEASURED "chore(release): v0.1.0" → ✅ MATCH ← the form the issue says is rejected MEASURED "chore(release): prepare v0.1.0" → ✅ MATCH MEASURED "chore(foo): v1.0.0" → 🔴 no match (negative control — regex discriminates) ``` **The issue's premise — "regex expects `prepare`, template omits it → mismatch" — is false on `main`. Both forms match.** ### The generator already emits `prepare` where it matters (MEASURED) ``` release-prep.sh:730 git commit -m "chore(release): prepare ${NEW_TAG}" ← COMMIT: has "prepare" release-prep.sh:763 PR_TITLE="chore(release): ${NEW_TAG}" ← PR TITLE: bare, BY DESIGN ``` Line 763's bare PR title is **intentional and documented** — `release-decide.sh:454` describes "the squash-merge style (subject = PR title = `chore(release): vX.Y.Z`, no 'prepare')". The optional-`prepare` regex exists *specifically* to accept it. **Adding `prepare` to line 763 would undo the #331/#417 squash-merge design, not fix a bug.** ### Timeline falsifies the regex-mismatch cause (MEASURED) ``` 2026-07-03 #331 landed — regex made prepare OPTIONAL (bare form accepted) 2026-07-07 v0.32.0 cut (includes #450 → includes #331) 2026-07-09 tmux-tell v0.30.0 hit 2026-07-10 ember v0.1.0 hit + this issue filed ``` 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 1. **Most likely: close as resolved-by-#331** — no generator/validator drift exists on `main`; both emitted forms match. The empirical hits are explained by stale pins (now bumped), not a live template bug. 2. **If there's a real unexplained failure**, point me at the actual skip-gate log from the tmux-tell/ember cut and I'll chase the true cause — but it isn't the bare-subject regex. 3. **The one thing I would NOT do**: add `prepare` to 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
Owner

🔴 Correcting my own prior comment — I probed the stale-pin ASSERTED claim, 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:

MEASURED  tmux-tell v0.30.0 (07-09):  release.yml pinned  reusable-release.yml@v0.20.0
          v0.20.0 cut 2026-07-02, PRE-#331. Its regex (verified at the tag):
            PREP_SUBJECT_RE='...:[[:space:]]+prepare[[:space:]]+v?...'   ← "prepare" MANDATORY, no optional group
          → a bare squash-merge subject was genuinely REJECTED → STALE PIN. Real, and fixed by the bump to v0.32.0.

MEASURED  ember v0.1.0 (07-10):       release.yml pinned  reusable-release.yml@v0.32.0  (at the v0.1.0 tag)
          v0.32.0 contains #331 (is-ancestor verified); its regex is the OPTIONAL-prepare form.
          → the bare form MATCHES on v0.32.0 → NOT a stale pin, NOT a subject-regex mismatch → UNEXPLAINED.

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

  • tmux-tell hit: explained + resolved (stale @v0.20.0, bumped to v0.32.0). No action.
  • 🔴 ember hit: genuinely unexplained. On v0.32.0 the bare subject matches, so whatever skip-gated ember's v0.1.0 cut, it was not the bare-subject PREP_SUBJECT_RE mismatch 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.
  • Template change: still do not implement. It fixes neither cause and would undo the #331/#417 squash-merge design at line 763.

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

## 🔴 Correcting my own prior comment — I probed the stale-pin `ASSERTED` claim, 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: ``` MEASURED tmux-tell v0.30.0 (07-09): release.yml pinned reusable-release.yml@v0.20.0 v0.20.0 cut 2026-07-02, PRE-#331. Its regex (verified at the tag): PREP_SUBJECT_RE='...:[[:space:]]+prepare[[:space:]]+v?...' ← "prepare" MANDATORY, no optional group → a bare squash-merge subject was genuinely REJECTED → STALE PIN. Real, and fixed by the bump to v0.32.0. MEASURED ember v0.1.0 (07-10): release.yml pinned reusable-release.yml@v0.32.0 (at the v0.1.0 tag) v0.32.0 contains #331 (is-ancestor verified); its regex is the OPTIONAL-prepare form. → the bare form MATCHES on v0.32.0 → NOT a stale pin, NOT a subject-regex mismatch → UNEXPLAINED. ``` ### 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 - ✅ **tmux-tell hit: explained + resolved** (stale `@v0.20.0`, bumped to v0.32.0). No action. - 🔴 **ember hit: genuinely unexplained.** On v0.32.0 the bare subject matches, so whatever skip-gated ember's v0.1.0 cut, **it was not the bare-subject `PREP_SUBJECT_RE` mismatch 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. - ❌ **Template change: still do not implement.** It fixes neither cause and would undo the #331/#417 squash-merge design at line 763. ### 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
Owner

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.

MEASURED  tmux-tell v0.30.0 (07-09):  @v0.20.0  → pre-#331, STRICT regex → stale pin, real, fixed by bump ✓
MEASURED  ember     v0.1.0  (07-10):  @v0.32.0  → #331 optional regex, bare form MATCHES → NOT a stale pin

What "resolved" means here, precisely

  • No generator↔validator drift exists on main (measured): the regex accepts both forms since #331 (07-03); the generator emits prepare on 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.
  • tmux-tell's fire is fully explained and fixed: stale @v0.20.0 (pre-#331 strict regex rejected the bare squash subject) → resolved by the pin-bump to v0.32.0.
  • ember's fire was on v0.32.0, where the bare form already matches — so it was not the subject-regex mismatch this issue names. The "subject-mismatch" attribution in the body is unverified testimony (author's recall now fuzzy) and is contradicted by the v0.32.0 match. No live-bug signature survives on 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:

If any post-pin-bump cut skip-gates on a subject that DOES match PREP_SUBJECT_RE, that is a new/real bug — pull that run's CI log and reopen. A skip on a matching subject is the signature this close does not cover.

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 main to 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

## 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.** ``` MEASURED tmux-tell v0.30.0 (07-09): @v0.20.0 → pre-#331, STRICT regex → stale pin, real, fixed by bump ✓ MEASURED ember v0.1.0 (07-10): @v0.32.0 → #331 optional regex, bare form MATCHES → NOT a stale pin ``` ### What "resolved" means here, precisely - **No generator↔validator drift exists on `main`** (measured): the regex accepts both forms since #331 (07-03); the generator emits `prepare` on 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. - **tmux-tell's fire is fully explained and fixed**: stale `@v0.20.0` (pre-#331 strict regex rejected the bare squash subject) → resolved by the pin-bump to v0.32.0. - **ember's fire was on v0.32.0, where the bare form already matches** — so it was *not* the subject-regex mismatch this issue names. The "subject-mismatch" attribution in the body is unverified testimony (author's recall now fuzzy) and is contradicted by the v0.32.0 match. **No live-bug signature survives on `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: > **If any post-pin-bump cut skip-gates on a subject that DOES match `PREP_SUBJECT_RE`, that is a new/real bug — pull that run's CI log and reopen.** A skip on a *matching* subject is the signature this close does not cover. 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 `main` to 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
Owner

🔴 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; its display_title is authoritative.

The actual failing subject (from ember Actions run #9, id=16682, 16:27:34)

display_title:  "chore(release): v0.1.0 (#37)"
                                       ^^^^^^ Forgejo's squash-merge suffix — the PR number

Forgejo squash-merge appends (#NN) to the PR title. The prep-PR (#37) was squash-merged, so the commit subject is chore(release): v0.1.0 (#37), not the clean chore(release): v0.1.0 I tested earlier.

Why it skip-gates — MEASURED, and it's live on main

PREP_SUBJECT_RE = '...([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)$'    ← $ anchors right after the version

MEASURED  "chore(release): v0.1.0"          → ✅ MATCH   (clean — what I wrongly tested)
MEASURED  "chore(release): v0.1.0 (#37)"    → 🔴 NO MATCH  ← the $ rejects the " (#37)" suffix
MEASURED  main regex == v0.32.0 regex → the bug is LIVE on main, not historical

Who was right about what

  • Issue premise (a real subject-regex bug exists): RIGHT.
  • Issue's prescribed fix (add prepare): still wrong — "chore(release): prepare v0.1.0 (#37)" also fails (measured). The suffix is the problem, not the missing keyword.
  • @quartermaster's past-me tag (skip AT PREP_SUBJECT_RE, on v0.32.0): RIGHT on gate + version. Only the "missing prepare" sub-diagnosis was off. I was too quick to call it "unverified testimony" — the gate attribution was correct.
  • My "no live bug" close: WRONG. I made an absence claim without testing the actual failing input — I tested my idea of the bare subject (clean), not the real one (with the (#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 prepare optional 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

PREP_SUBJECT_RE='^chore\(release\):[[:space:]]+(prepare[[:space:]]+)?v?([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)([[:space:]]+\(#[0-9]+\))?$'
#                                                                                                              ^^^^^^^^^^^^^^^^^^^^^^^^^ optional Forgejo squash-merge suffix
MEASURED  "chore(release): v0.1.0"                     → ✅
MEASURED  "chore(release): v0.1.0 (#37)"               → ✅   (the fix)
MEASURED  "chore(release): prepare v1.2.3-rc.1 (#412)" → ✅
MEASURED  "chore(foo): v1.0.0"                          → 🔴  (negative control — no over-match)

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_title was the log I couldn't otherwise reach. @bosun — reopening + implementing; this is a genuine live bug affecting every squash-merging consumer.

## 🔴 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; its `display_title` is authoritative. ### The actual failing subject (from ember Actions run #9, `id=16682`, 16:27:34) ``` display_title: "chore(release): v0.1.0 (#37)" ^^^^^^ Forgejo's squash-merge suffix — the PR number ``` **Forgejo squash-merge appends ` (#NN)` to the PR title.** The prep-PR (#37) was squash-merged, so the commit subject is `chore(release): v0.1.0 (#37)`, not the clean `chore(release): v0.1.0` I tested earlier. ### Why it skip-gates — MEASURED, and it's live on `main` ``` PREP_SUBJECT_RE = '...([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)$' ← $ anchors right after the version MEASURED "chore(release): v0.1.0" → ✅ MATCH (clean — what I wrongly tested) MEASURED "chore(release): v0.1.0 (#37)" → 🔴 NO MATCH ← the $ rejects the " (#37)" suffix MEASURED main regex == v0.32.0 regex → the bug is LIVE on main, not historical ``` ### Who was right about what - **Issue premise (a real subject-regex bug exists):** ✅ RIGHT. - **Issue's prescribed fix (add `prepare`):** ❌ still wrong — `"chore(release): prepare v0.1.0 (#37)"` **also fails** (measured). The suffix is the problem, not the missing keyword. - **@quartermaster's past-me tag (skip AT PREP_SUBJECT_RE, on v0.32.0):** ✅ RIGHT on gate + version. Only the *"missing prepare"* sub-diagnosis was off. I was too quick to call it "unverified testimony" — the gate attribution was correct. - **My "no live bug" close:** ❌ WRONG. I made an **absence claim without testing the actual failing input** — I tested my idea of the bare subject (clean), not the real one (with the `(#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 `prepare` optional 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 ```sh PREP_SUBJECT_RE='^chore\(release\):[[:space:]]+(prepare[[:space:]]+)?v?([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)([[:space:]]+\(#[0-9]+\))?$' # ^^^^^^^^^^^^^^^^^^^^^^^^^ optional Forgejo squash-merge suffix ``` ``` MEASURED "chore(release): v0.1.0" → ✅ MEASURED "chore(release): v0.1.0 (#37)" → ✅ (the fix) MEASURED "chore(release): prepare v1.2.3-rc.1 (#412)" → ✅ MEASURED "chore(foo): v1.0.0" → 🔴 (negative control — no over-match) ``` ### 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_title` was the log I couldn't otherwise reach. @bosun — reopening + implementing; this is a genuine live bug affecting every squash-merging consumer.
bosun closed this issue 2026-07-23 23:58:03 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#487
No description provided.