fix(cut): push-during-cut cancels cut workflow — orphaned CHANGELOG entry + duplicate-entry drift on next cut (v1.0.0 must-fix) #417

Closed
opened 2026-07-05 22:27:41 +02:00 by bosun · 0 comments
Owner

Empirical finding

Demo repo (#382 tic-tac-toe on Codeberg) hit this in-the-wild during v0.1.1 → v0.2.0 cycle 2026-07-05 evening. Full sequence:

  1. PR#3 (rolling release-prep for v0.1.1) merged at commit d355aa7 (CHANGELOG got new [v0.1.1] section, VERSION bumped to 0.1.1)
  2. release-toolkit workflow (run 5305304) queued to cut v0.1.1 (create tag + release + manifest update)
  3. Before the runner picked up, adopter pushed commit ab01c89 (feat: display version + hosted URL link)
  4. Codeberg CI cancelled workflow 5305304 mid-queue — its default behavior on new pushes to the same branch (or Forgejo Actions equivalent concurrency behavior)
  5. Workflow 5305718 fired for ab01c89 push
  6. release-decide.sh walked commits since manifest (v0.1.0), detected feat → minor bump, decided next release is v0.2.0
  7. Rolling PR#4 opened for v0.2.0

Observable failure

  • v0.1.1 tag/release never created (workflow cancelled before that step)
  • CHANGELOG has orphaned [v0.1.1] section (from PR#3 merge)
  • Manifest still says v0.1.0 (not updated)
  • PR#4 v0.2.0 CHANGELOG diff includes DUPLICATE entry: the a11y fix ('keyboard-navigable cells for accessibility') appears in BOTH the new [v0.2.0] section (walked from commit) AND the pre-existing [v0.1.1] section (from PR#3 merge)

release-toolkit's release-prep.sh generates CHANGELOG entries by walking commits since manifest — it does NOT check whether entries are already documented under an unclaimed version block. Adopter merging PR#4 gets a CHANGELOG with the a11y fix listed twice.

Root cause hypothesis

Shared CI runners (Codeberg, others) cancel in-flight workflow runs when new pushes arrive for the same branch — standard resource efficiency behavior. release-toolkit's cut workflow lacks concurrency protection, so the cut becomes cancellable mid-work. Once cancelled, the intermediate state (CHANGELOG updated but no tag/release) is not recoverable by the next run — the next run just walks commits again, generating duplicate entries.

Fix candidates

Option A — Forgejo Actions concurrency block:

concurrency:
  group: release-cut-${{ github.ref }}
  cancel-in-progress: false

Simplest fix. Subsequent pushes wait in queue rather than cancel the in-flight run. Cut completes before new work begins.

Option B — cut-branch fork approach (operator's suggestion):
The cut happens on a dedicated ephemeral branch (e.g. cut/v0.1.1) rather than master. Master pushes don't touch the cut branch's workflow. Once cut completes, cleanup the branch.

Option C — Idempotent cut with state recovery:
The cut workflow checks on start if there's a partial cut in progress (CHANGELOG has entry, VERSION bumped, but no tag). If so, complete the cut first (create tag + release + manifest update for the pending version) before doing new work.

Blocking rationale

Operator directive 2026-07-05: v1.0.0 must-fix. release-toolkit shipping a v1.0.0 that silently drops releases under normal adopter push cadence is not v1.0.0-quality. Adopter operational trust requires cuts to be reliable under real-world push patterns.

Verification AC

  • Push-during-cut simulation: adopter pushes a new commit while cut workflow is queued. Expected behavior: cut completes for the intended version; subsequent push starts a new cut cycle for the new state.
  • No duplicate CHANGELOG entries across cut cycles
  • Manifest always reflects the actually-tagged latest release
  • Empirical validation via demo repo (#382 tic-tac-toe) rerun or synthetic test
  • Empirical anchor: #382 demo repo (v0.1.1 → v0.2.0 cycle 2026-07-05 evening)
  • Sibling class of race-condition bugs (Herald PR#390's dismiss_stale, Surveyor's stale-stamp catch)
  • Verify-after-mutation discipline (QM banked) argues for state-check-before-cut

Anchor

Operator ratified filing 2026-07-05 as v1.0.0 must-fix. Empirical evidence in-hand from demo repo cycle. Fix candidate A (concurrency block) is simplest; QM's substrate call whether A alone is sufficient or B/C also warranted.

## Empirical finding Demo repo (#382 tic-tac-toe on Codeberg) hit this in-the-wild during v0.1.1 → v0.2.0 cycle 2026-07-05 evening. Full sequence: 1. PR#3 (rolling release-prep for v0.1.1) merged at commit d355aa7 (CHANGELOG got new [v0.1.1] section, VERSION bumped to 0.1.1) 2. release-toolkit workflow (run 5305304) queued to cut v0.1.1 (create tag + release + manifest update) 3. Before the runner picked up, adopter pushed commit ab01c89 (feat: display version + hosted URL link) 4. **Codeberg CI cancelled workflow 5305304 mid-queue** — its default behavior on new pushes to the same branch (or Forgejo Actions equivalent concurrency behavior) 5. Workflow 5305718 fired for ab01c89 push 6. release-decide.sh walked commits since manifest (v0.1.0), detected feat → minor bump, decided next release is v0.2.0 7. Rolling PR#4 opened for v0.2.0 ## Observable failure - **v0.1.1 tag/release never created** (workflow cancelled before that step) - **CHANGELOG has orphaned [v0.1.1] section** (from PR#3 merge) - **Manifest still says v0.1.0** (not updated) - **PR#4 v0.2.0 CHANGELOG diff includes DUPLICATE entry**: the a11y fix ('keyboard-navigable cells for accessibility') appears in BOTH the new [v0.2.0] section (walked from commit) AND the pre-existing [v0.1.1] section (from PR#3 merge) release-toolkit's release-prep.sh generates CHANGELOG entries by walking commits since manifest — it does NOT check whether entries are already documented under an unclaimed version block. Adopter merging PR#4 gets a CHANGELOG with the a11y fix listed twice. ## Root cause hypothesis Shared CI runners (Codeberg, others) cancel in-flight workflow runs when new pushes arrive for the same branch — standard resource efficiency behavior. release-toolkit's cut workflow lacks concurrency protection, so the cut becomes cancellable mid-work. Once cancelled, the intermediate state (CHANGELOG updated but no tag/release) is not recoverable by the next run — the next run just walks commits again, generating duplicate entries. ## Fix candidates **Option A — Forgejo Actions concurrency block**: ```yaml concurrency: group: release-cut-${{ github.ref }} cancel-in-progress: false ``` Simplest fix. Subsequent pushes wait in queue rather than cancel the in-flight run. Cut completes before new work begins. **Option B — cut-branch fork approach** (operator's suggestion): The cut happens on a dedicated ephemeral branch (e.g. `cut/v0.1.1`) rather than master. Master pushes don't touch the cut branch's workflow. Once cut completes, cleanup the branch. **Option C — Idempotent cut with state recovery**: The cut workflow checks on start if there's a partial cut in progress (CHANGELOG has entry, VERSION bumped, but no tag). If so, complete the cut first (create tag + release + manifest update for the pending version) before doing new work. ## Blocking rationale **Operator directive 2026-07-05: v1.0.0 must-fix**. release-toolkit shipping a v1.0.0 that silently drops releases under normal adopter push cadence is not v1.0.0-quality. Adopter operational trust requires cuts to be reliable under real-world push patterns. ## Verification AC - Push-during-cut simulation: adopter pushes a new commit while cut workflow is queued. Expected behavior: cut completes for the intended version; subsequent push starts a new cut cycle for the new state. - No duplicate CHANGELOG entries across cut cycles - Manifest always reflects the actually-tagged latest release - Empirical validation via demo repo (#382 tic-tac-toe) rerun or synthetic test ## Related - Empirical anchor: #382 demo repo (v0.1.1 → v0.2.0 cycle 2026-07-05 evening) - Sibling class of race-condition bugs (Herald PR#390's dismiss_stale, Surveyor's stale-stamp catch) - Verify-after-mutation discipline (QM banked) argues for state-check-before-cut ## Anchor Operator ratified filing 2026-07-05 as v1.0.0 must-fix. Empirical evidence in-hand from demo repo cycle. Fix candidate A (concurrency block) is simplest; QM's substrate call whether A alone is sufficient or B/C also warranted.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#417
No description provided.