feat(cut): reduce path-α cut workflow self-cancel noise — re-order or defer manifest direct-push #139

Closed
opened 2026-06-26 23:06:29 +02:00 by quartermaster · 0 comments

The phenomenon

When the cut path runs under path-α (release-bot configured + RELEASE_TOKEN_OVERRIDE set), the workflow's own manifest direct-push to main fires a new push:main event. Forgejo Actions then cancels the older in-flight run in favor of the new one. The cut substantively completes (release-create + manifest commit + main update all succeed) BEFORE cancellation interrupts the in-flight run's post-steps.

Empirical observations

n=4 path-α cuts, all showed the same self-cancel pattern:

Cut Workflow tasks Pattern
v0.8.0 task 13307 cancelled → operator manual re-run → 13310 failure (409) Pre-idempotency; surfaced #128
v0.9.0 task 13308 cancelled (same way) Pre-idempotency; same shape
v0.10.0 task 13307 cancelled → operator manual re-run → 13310 failure (409) Pre-idempotency; the empirical artifact that filed #128
v0.10.2 task 13373 cancelled → 13374 ran post-cut, mode=noop, success Post-idempotency (#128/#131): no re-run needed; cancellation is benign noise

So the cancellation pattern is reproducible + structural to path-α, not a one-off. The idempotency work in #131 handles the failure mode (re-run after cancel → idempotent skip) but doesn't prevent the cancellation itself.

Why path-γ doesn't have this

Path-γ's manifest update goes through a PR (operator merges later), not a direct push. The cut workflow doesn't trigger a new push:main event on itself; no self-cancel.

Possible mitigations

(A) Re-order: manifest direct-push as the LAST substantive step

Move the manifest direct-push to the END of the cut step's bash. Post-cancel interruption then only loses trivial work (e.g., the stale-rolling-PR cleanup runs BEFORE the push). The cancellation still happens, but it interrupts nothing load-bearing.

Pro: simplest fix; ~10 lines of step-ordering in _release.yml.
Con: doesn't eliminate the cancellation itself; just minimizes the blast radius.

(B) Forgejo concurrency block on the workflow

concurrency: is a documented banned top-level key in this Forgejo runtime (per tests/workflows.bats "no top-level concurrency / run-name / timeout-minutes" guard — Forgejo silently rejects). So this isn't available unless that constraint has loosened.

Pro: would prevent the cancel entirely.
Con: blocked by the substrate (the workflow.bats guard documents the failure mode).

(C) [skip-ci] marker on the manifest commit

If Forgejo Actions respects [skip-ci] / [skip ci] in commit messages, the manifest commit could include it and not retrigger the workflow.

Pro: clean — the manifest push doesn't fire a new workflow run; no cancel.
Con: needs verification that Forgejo respects the marker. If it doesn't, this mitigation can't fly without an upstream Forgejo change.

(D) Push the manifest with a paths filter that excludes the workflow

The release.yml workflow could use paths-ignore: or paths: to exclude commits that ONLY touch .release-toolkit-manifest.json.

Pro: only affects manifest-only commits; other pushes (substantive code) still fire the workflow.
Con: relies on Forgejo Actions respecting paths/paths-ignore correctly. Needs verification.

(E) Split cut + manifest into two workflows

The cut workflow handles release-create; a separate "manifest-update" workflow handles the manifest commit. Sequencing via workflow_call chaining.

Pro: clean separation of concerns.
Con: significant complexity; would need redesign of the _release.yml cut path; possibly not worth it for cosmetic noise.

My lean

(A) re-order as the minimum-viable fix, with empirical investigation of (C) [skip-ci] + (D) paths-ignore: as potentially-better cleaner alternatives.

Try (C) first — single-line addition to the manifest commit message. If Forgejo respects it, that's the cleanest. If not, fall back to (A) or (D).

Severity: cosmetic-noise

The substrate state is correct after every path-α cut (idempotent code path + the cancellation being structurally benign per the post-idempotency v0.10.2 evidence). This tracker reduces noise + makes the workflow-run UI cleaner; doesn't fix a substrate bug.

Implementation surface

  • (A): re-order steps in _release.yml cut path (~10 lines of YAML)
  • (C) probe: add [skip-ci] to the manifest commit message + observe if Forgejo respects it
  • (D) probe: add paths-ignore: [.release-toolkit-manifest.json] to release.yml's push trigger + observe

All three together: ~50 lines of changes + 1-2 cuts of empirical verification.

Refs

  • Operator engagement 2026-06-26 23:??: surfaced the cancellation question after Surveyor's 543f close on the v0.10.2 cut
  • Empirical artifacts: tasks 13307 (v0.8.0), 13308 (v0.9.0), 13307→13310 (v0.10.0), 13373 (v0.10.2) — all path-α cuts showed the self-cancel
  • Related: #128 idempotency (handles the failure-mode of re-run-after-cancel), #131 implementation
  • Sister substrate: ADR-0007 path-α direct-push mechanism (the structural source of the self-cancel)
  • Forgejo Actions constraint: tests/workflows.bats "no top-level concurrency / run-name / timeout-minutes" guard (rules out option B)
## The phenomenon When the cut path runs under path-α (release-bot configured + `RELEASE_TOKEN_OVERRIDE` set), the workflow's own manifest direct-push to `main` fires a new `push:main` event. Forgejo Actions then cancels the older in-flight run in favor of the new one. The cut substantively completes (release-create + manifest commit + main update all succeed) BEFORE cancellation interrupts the in-flight run's post-steps. ## Empirical observations n=4 path-α cuts, all showed the same self-cancel pattern: | Cut | Workflow tasks | Pattern | |---|---|---| | v0.8.0 | task 13307 cancelled → operator manual re-run → 13310 failure (409) | Pre-idempotency; surfaced #128 | | v0.9.0 | task 13308 cancelled (same way) | Pre-idempotency; same shape | | v0.10.0 | task 13307 cancelled → operator manual re-run → 13310 failure (409) | Pre-idempotency; the empirical artifact that filed #128 | | v0.10.2 | task 13373 cancelled → 13374 ran post-cut, mode=noop, success | **Post-idempotency (#128/#131)**: no re-run needed; cancellation is benign noise | So the cancellation pattern is **reproducible + structural** to path-α, not a one-off. The idempotency work in [#131](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/131) handles the failure mode (re-run after cancel → idempotent skip) but doesn't prevent the cancellation itself. ## Why path-γ doesn't have this Path-γ's manifest update goes through a PR (operator merges later), not a direct push. The cut workflow doesn't trigger a new `push:main` event on itself; no self-cancel. ## Possible mitigations ### (A) Re-order: manifest direct-push as the LAST substantive step Move the manifest direct-push to the END of the cut step's bash. Post-cancel interruption then only loses trivial work (e.g., the stale-rolling-PR cleanup runs BEFORE the push). The cancellation still happens, but it interrupts nothing load-bearing. **Pro**: simplest fix; ~10 lines of step-ordering in `_release.yml`. **Con**: doesn't eliminate the cancellation itself; just minimizes the blast radius. ### (B) Forgejo concurrency block on the workflow `concurrency:` is a documented banned top-level key in this Forgejo runtime (per `tests/workflows.bats` "no top-level concurrency / run-name / timeout-minutes" guard — Forgejo silently rejects). So this isn't available unless that constraint has loosened. **Pro**: would prevent the cancel entirely. **Con**: blocked by the substrate (the workflow.bats guard documents the failure mode). ### (C) `[skip-ci]` marker on the manifest commit If Forgejo Actions respects `[skip-ci]` / `[skip ci]` in commit messages, the manifest commit could include it and not retrigger the workflow. **Pro**: clean — the manifest push doesn't fire a new workflow run; no cancel. **Con**: needs verification that Forgejo respects the marker. If it doesn't, this mitigation can't fly without an upstream Forgejo change. ### (D) Push the manifest with a paths filter that excludes the workflow The release.yml workflow could use `paths-ignore:` or `paths:` to exclude commits that ONLY touch `.release-toolkit-manifest.json`. **Pro**: only affects manifest-only commits; other pushes (substantive code) still fire the workflow. **Con**: relies on Forgejo Actions respecting `paths`/`paths-ignore` correctly. Needs verification. ### (E) Split cut + manifest into two workflows The cut workflow handles release-create; a separate "manifest-update" workflow handles the manifest commit. Sequencing via `workflow_call` chaining. **Pro**: clean separation of concerns. **Con**: significant complexity; would need redesign of the `_release.yml` cut path; possibly not worth it for cosmetic noise. ### My lean **(A) re-order as the minimum-viable fix**, with empirical investigation of (C) `[skip-ci]` + (D) `paths-ignore:` as potentially-better cleaner alternatives. Try (C) first — single-line addition to the manifest commit message. If Forgejo respects it, that's the cleanest. If not, fall back to (A) or (D). ## Severity: cosmetic-noise The substrate state is correct after every path-α cut (idempotent code path + the cancellation being structurally benign per the post-idempotency v0.10.2 evidence). This tracker reduces noise + makes the workflow-run UI cleaner; doesn't fix a substrate bug. ## Implementation surface - (A): re-order steps in `_release.yml` cut path (~10 lines of YAML) - (C) probe: add `[skip-ci]` to the manifest commit message + observe if Forgejo respects it - (D) probe: add `paths-ignore: [.release-toolkit-manifest.json]` to release.yml's push trigger + observe All three together: ~50 lines of changes + 1-2 cuts of empirical verification. ## Refs - **Operator engagement 2026-06-26 23:??**: surfaced the cancellation question after Surveyor's 543f close on the v0.10.2 cut - **Empirical artifacts**: tasks 13307 (v0.8.0), 13308 (v0.9.0), 13307→13310 (v0.10.0), 13373 (v0.10.2) — all path-α cuts showed the self-cancel - **Related**: [#128](https://git.frankenbit.de/frankenbit/release-toolkit/issues/128) idempotency (handles the failure-mode of re-run-after-cancel), [#131](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/131) implementation - **Sister substrate**: ADR-0007 path-α direct-push mechanism (the structural source of the self-cancel) - **Forgejo Actions constraint**: `tests/workflows.bats` "no top-level concurrency / run-name / timeout-minutes" guard (rules out option B)
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#139
No description provided.