bug(_release.yml): stale rolling PR not auto-closed after Layer 2 fall-through + later cut success #87

Closed
opened 2026-06-26 00:51:01 +02:00 by quartermaster · 0 comments

Symptom

When the Layer 2 cut-safeguard race-condition (see sibling tracker for the Layer-2 race) fires protective fall-through, mode=update opens a new rolling PR as a precaution. If the cut is later dispatched manually + succeeds, the fall-through-opened rolling PR is left open as stale state — there's no cleanup step.

In the v0.5.0 cut: PR #84 was opened by the fall-through at 00:40:40. The cut was later dispatched and succeeded (v0.5.0 draft → operator Publish → GA). PR #84 sat open for ~7 minutes until I manually closed it. Operator's read: "first step on the stairway to version hell" — multiple "v0.5.0 release PRs" co-existing post-cut is confusing.

Why it matters

In the steady state (no race condition), the rolling PR's identity reflects "what would ship next." When a cut happens, the rolling PR's content is consumed → next workflow run sees mode=noop or mode=update on the next bump-target. Coherent.

In the race-condition state, the fall-through opens a rolling PR for the SAME version that just shipped. After the cut succeeds, the rolling PR is stale (mergeable=false in our case because head=force-pushed-rolling-state, but in some cases it might still mergeable=true → footgun).

Proposed fix

Two complementary cleanup mechanisms:

Cleanup point Action
At cut success _release.yml's cut path checks if there's an open rolling PR matching the just-cut version (title contains "v$CUT_VERSION") + closes it as stale. Substrate-honest cleanup at the success-event.
At next update fire release-prep.sh --rolling-mode checks the manifest's last_released_version against the current next_version. If they match (= would re-cut the version that already shipped), short-circuit + close any open rolling PR for that version + emit mode=already-released.

Either alone unblocks the operator-experience. Both together is defense-in-depth.

Edge case

If a SECOND cut needs to happen at the same version (e.g., recall + re-cut), the cleanup logic should respect that — the fall-through-stale detection should specifically check for "rolling PR open AFTER the cut commit landed" rather than "rolling PR open AT the cut version."

Refs

  • Surfaced: 2026-06-26 v0.5.0 cut — PR #84 left open ~7 min until manual close
  • Operator framing: "first step on the stairway to version hell" (this tracker's spiritual purpose)
  • Sibling: Layer 2 race tracker (root cause of the fall-through that creates the stale PR)
  • Manual close action: closed at 00:48 with explanatory comment on PR #84
  • v0.5.1 substrate-correctness sweep candidate alongside #64 + #56 + the Layer 2 race tracker
## Symptom When the Layer 2 cut-safeguard race-condition (see sibling tracker for the Layer-2 race) fires protective fall-through, mode=update opens a new rolling PR as a precaution. If the cut is later dispatched manually + succeeds, the fall-through-opened rolling PR is **left open as stale state** — there's no cleanup step. In the v0.5.0 cut: PR #84 was opened by the fall-through at `00:40:40`. The cut was later dispatched and succeeded (v0.5.0 draft → operator Publish → GA). PR #84 sat open for ~7 minutes until I manually closed it. Operator's read: "first step on the stairway to version hell" — multiple "v0.5.0 release PRs" co-existing post-cut is confusing. ## Why it matters In the steady state (no race condition), the rolling PR's identity reflects "what would ship next." When a cut happens, the rolling PR's content is consumed → next workflow run sees mode=noop or mode=update on the next bump-target. Coherent. In the race-condition state, the fall-through opens a rolling PR for the SAME version that just shipped. After the cut succeeds, the rolling PR is stale (mergeable=false in our case because head=force-pushed-rolling-state, but in some cases it might still mergeable=true → footgun). ## Proposed fix Two complementary cleanup mechanisms: | Cleanup point | Action | |---|---| | **At cut success** | `_release.yml`'s cut path checks if there's an open rolling PR matching the just-cut version (title contains "v$CUT_VERSION") + closes it as stale. Substrate-honest cleanup at the success-event. | | **At next update fire** | `release-prep.sh --rolling-mode` checks the manifest's `last_released_version` against the current `next_version`. If they match (= would re-cut the version that already shipped), short-circuit + close any open rolling PR for that version + emit `mode=already-released`. | Either alone unblocks the operator-experience. Both together is defense-in-depth. ## Edge case If a SECOND cut needs to happen at the same version (e.g., recall + re-cut), the cleanup logic should respect that — the fall-through-stale detection should specifically check for "rolling PR open AFTER the cut commit landed" rather than "rolling PR open AT the cut version." ## Refs - **Surfaced**: 2026-06-26 v0.5.0 cut — PR #84 left open ~7 min until manual close - **Operator framing**: "first step on the stairway to version hell" (this tracker's spiritual purpose) - **Sibling**: Layer 2 race tracker (root cause of the fall-through that creates the stale PR) - **Manual close action**: closed at 00:48 with explanatory comment on PR #84 - **v0.5.1 substrate-correctness sweep candidate** alongside #64 + #56 + the Layer 2 race tracker
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#87
No description provided.