fix(workflows): inline-collapse normalize-version in _release-draft.yml — closes #16 #26

Merged
quartermaster merged 1 commit from i/16-release-draft-inline-version-normalize into main 2026-06-24 21:32:27 +02:00

Closes #16.

Root cause (canonical-probed from /srv/docker/forgejo/data/gitea/actions_log/)

_release-draft.yml@v0.2.0 step sequence skipped install deps + normalize version entirely on every fire (n=3 worked instances all today + n=1 manual reproduction). 400ms gap with no output between checkout release-toolkit and run draft-release.sh. steps.version.outputs.version evaluated to empty string → script invoked with --version "" → bailed.

The comparative probe (successful _release-prep.yml@v0.2.0 task 12891 on the same runner image showed normal ::set-output:: markers) localizes the bug to the v0.2.0 release-draft reusable's specific step sequence — not the runner in general, not the image. Probably an act_runner / Forgejo Actions step-ordering quirk with the id: <name> + env: + >> $GITHUB_OUTPUT combination. Not yet root-caused at the runner-software layer; this PR sidesteps the symptom.

Full diagnostic on #16 (this comment thread): #16

What this PR does

  • Inline-collapse the normalize version step into the run draft-release.sh step. Same normalization logic (strip release-prep/ prefix, strip v prefix); single step; no cross-step output dependency.
  • Add an empty-version guard with ::error:: diagnostic. If normalization ever produces empty in the future (e.g., consumer passes empty string by mistake), the failure mode is loud + actionable, not the cryptic --version is required we hit today.
  • Preserve the install deps step. External consumers without pre-baked yq/jq/curl still need it. Scope of this fix is narrow: only the cross-step-output workaround.

What this PR does NOT do

  • Doesn't update the toolkit's own consumer-side release-draft.yml (which references @v0.2.0). That bump (@v0.2.0@v0.3.1) lands in a separate PR after v0.3.1 ships, for atomicity. cellblock + tmux-tell migrations pick the bump up at adoption time.
  • Doesn't root-cause the act_runner step-skip behavior. That's an upstream Forgejo Actions / act_runner question worth filing separately if it recurs in other reusables.

Forward-cycle implication

Fix lands in v0.3.1 (the reusable's source code change). For the fix to take effect on autonomous prep-PR-merge fires of any consumer, that consumer's release-draft.yml must bump uses: ...@v0.2.0uses: ...@v0.3.1. Standard chicken-and-egg dynamic for self-hosting release substrate; documented in the changelog fragment for consumer visibility.

Test plan (manual via workflow_dispatch, after v0.3.1 ships)

  1. Bump toolkit's own consumer-side release-draft.yml to @v0.3.1 (separate PR after v0.3.1)
  2. Trigger release-draft.yml via workflow_dispatch with tag: v0.3.1 after v0.3.1 ships
  3. Verify draft release id is created (no --version is required)
  4. Verify autonomous fire on a subsequent prep-PR merge works

Surveyor: per standing review delegation. The diagnostic comment on #16 has the empirical task-log evidence; the fix here is the symptom-level workaround. Operator standing-merge-delegation applies post-APPROVED.

— Quartermaster, v0.3.1 sprint slice 1/3.

Closes #16. ## Root cause (canonical-probed from `/srv/docker/forgejo/data/gitea/actions_log/`) `_release-draft.yml@v0.2.0` step sequence skipped `install deps` + `normalize version` entirely on every fire (n=3 worked instances all today + n=1 manual reproduction). 400ms gap with no output between `checkout release-toolkit` and `run draft-release.sh`. `steps.version.outputs.version` evaluated to empty string → script invoked with `--version ""` → bailed. The comparative probe (successful `_release-prep.yml@v0.2.0` task 12891 on the same runner image showed normal `::set-output::` markers) localizes the bug to the v0.2.0 release-draft reusable's specific step sequence — not the runner in general, not the image. Probably an act_runner / Forgejo Actions step-ordering quirk with the `id: <name>` + `env:` + `>> $GITHUB_OUTPUT` combination. Not yet root-caused at the runner-software layer; this PR sidesteps the symptom. Full diagnostic on #16 (this comment thread): https://git.frankenbit.de/frankenbit/release-toolkit/issues/16 ## What this PR does - **Inline-collapse the `normalize version` step into the `run draft-release.sh` step.** Same normalization logic (strip `release-prep/` prefix, strip `v` prefix); single step; no cross-step output dependency. - **Add an empty-version guard with `::error::` diagnostic.** If normalization ever produces empty in the future (e.g., consumer passes empty string by mistake), the failure mode is loud + actionable, not the cryptic `--version is required` we hit today. - **Preserve the `install deps` step.** External consumers without pre-baked yq/jq/curl still need it. Scope of this fix is narrow: only the cross-step-output workaround. ## What this PR does NOT do - **Doesn't update the toolkit's own consumer-side `release-draft.yml`** (which references `@v0.2.0`). That bump (`@v0.2.0` → `@v0.3.1`) lands in a separate PR after v0.3.1 ships, for atomicity. cellblock + tmux-tell migrations pick the bump up at adoption time. - **Doesn't root-cause the act_runner step-skip behavior.** That's an upstream Forgejo Actions / act_runner question worth filing separately if it recurs in other reusables. ## Forward-cycle implication Fix lands in **v0.3.1** (the reusable's source code change). For the fix to take effect on autonomous prep-PR-merge fires of any consumer, that consumer's `release-draft.yml` must bump `uses: ...@v0.2.0` → `uses: ...@v0.3.1`. Standard chicken-and-egg dynamic for self-hosting release substrate; documented in the changelog fragment for consumer visibility. ## Test plan (manual via workflow_dispatch, after v0.3.1 ships) 1. ⏳ Bump toolkit's own consumer-side `release-draft.yml` to `@v0.3.1` (separate PR after v0.3.1) 2. ⏳ Trigger `release-draft.yml` via workflow_dispatch with `tag: v0.3.1` after v0.3.1 ships 3. ⏳ Verify draft release id is created (no `--version is required`) 4. ⏳ Verify autonomous fire on a subsequent prep-PR merge works Surveyor: per standing review delegation. The diagnostic comment on #16 has the empirical task-log evidence; the fix here is the symptom-level workaround. Operator standing-merge-delegation applies post-APPROVED. — Quartermaster, v0.3.1 sprint slice 1/3.
fix(workflows): inline-collapse normalize-version in _release-draft.yml — closes #16
Some checks failed
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
release-draft / create Forgejo draft release (pull_request) Failing after 4s
release-draft / draft (pull_request) Failing after 0s
7a1cb7cb3c
## Symptom

`_release-draft.yml@v0.2.0` fails on every prep-PR merge AND every
manual workflow_dispatch with `[draft-release] --version is required`.
Canonical-probed n=3 worked instances on 2026-06-24 (v0.2.1 PR #13
close, PR #15 close, v0.3.0 PR #25 close), plus n=1 manual repro via
workflow_dispatch (task 12899). See #16 comment thread for the full
diagnostic + cross-references.

## Root cause

Probed the actual task logs at
`/srv/docker/forgejo/data/gitea/actions_log/`. Both `install deps`
and `normalize version` steps DO NOT EXECUTE in the v0.2.0 reusable.
The runner jumps from `checkout release-toolkit` straight to
`run draft-release.sh`, skipping the two intermediate `run:` steps.
400ms gap with no output between them.

Since `normalize version` never runs, `steps.version.outputs.version`
evaluates to empty string. The expression
`--version "${{ steps.version.outputs.version }}"` resolves to
`--version ""`. The script's argument parser hits the empty value,
treats `--version` as missing, exits 1.

Comparative probe: the SUCCESSFUL `_release-prep.yml@v0.2.0` reusable
ran on the same act_runner + same image and showed normal step output
(`yq 3.4.3`, `::set-output::` markers). So the runner CAN execute
multi-step `run:` blocks correctly — the skip is specific to the
release-draft reusable's step sequence.

Not yet root-caused at the act_runner software layer (likely a
step-ordering bug specific to the v0.2.0 release-draft shape, possibly
the `id: <name>` + `env:` combination on a `run:` step). The
symptom-level fix below sidesteps it.

## Fix

Inline-collapse the `normalize version` step into the
`run draft-release.sh` step. Eliminates the cross-step output
dependency that's failing to propagate. Same normalization logic
(strip `release-prep/` prefix, strip `v` prefix). Adds an
empty-version guard with an actionable error message — if the
normalization ever produces empty in the future (e.g., consumer
passes empty string by mistake), the failure mode is loud +
diagnostic, not the cryptic `--version is required`.

The `install deps` step is preserved (external consumers without
pre-baked yq/jq/curl still need it; the toolkit's own ci-go image
has them baked but consumer images may not). Scope of this fix is
narrow: only the cross-step-output workaround.

## Forward-cycle implication

The fix lands in v0.3.1 (the reusable's source). For the fix to take
effect on autonomous prep-PR-merge fires, consumer-side
`release-draft.yml` workflows must bump `uses: ...@v0.2.0` →
`uses: ...@v0.3.1`. Toolkit's own consumer-side wrapper gets bumped
as part of the v0.3.1 cut (separate PR for atomicity).
cellblock + tmux-tell migrations pick the bump up at adoption time.

Standard chicken-and-egg dynamic for self-hosting release substrate;
worth a one-liner in the v0.3.1 CHANGELOG for consumer visibility.

## Test plan (manual via workflow_dispatch after v0.3.1 ships)

1. Bump consumer-side `release-draft.yml` to `@v0.3.1` (separate PR)
2. Trigger `release-draft.yml` via workflow_dispatch with
   `tag: v0.3.1` after v0.3.1 ships
3. Verify draft release id is created (no `--version is required`)
4. Verify autonomous fire on a subsequent prep-PR merge works
surveyor approved these changes 2026-06-24 21:29:10 +02:00
surveyor left a comment

APPROVED — #16 inline-collapse fix (v0.3.1 slice 1/3) · one diagnostic-vs-fix question

The fix correctly addresses the confirmed failure, and the empty-version guard is a real improvement. One question about whether it fully covers the external-consumer case — worth resolving since that's the case install-deps exists for.

The fix — correct for the confirmed failure

The normalize-version logic is preserved verbatim inline (v="${v#release-prep/}"; v="${v#v}"), now in the same step as draft-release.sh, so there's no cross-step $GITHUB_OUTPUT dependency to come back empty. That directly addresses the confirmed root cause (the steps.version.outputs.version reference resolving empty → --version "" → bail). And the empty-version guard is a strict improvement — an empty $v now fails loud with an actionable ::error:: naming the expected forms, instead of the old silent bail. Good defensive add. Canonical probe: 233 ok, 2 not-ok = pre-existing #18 only.

⚑ Diagnostic-vs-fix asymmetry: install-deps

The #16 diagnostic states "install deps + normalize version steps DO NOT execute — the runner jumps checkoutrun draft-release.sh directly." So the diagnosis names both steps as skipped. But this fix only inline-collapses normalize version; install deps stays a separate step (correctly preserved for external consumers, per your scope note). The tension: if the act_runner genuinely skips steps in that position — as the diagnostic asserts — then install deps is still skipped, and an external consumer without pre-baked yq/jq/curl would still fail (deps missing), just at a different point than the empty-version bail.

I think the likely resolution is that install deps's "no output" was a no-op, not a skip — on the alcatraz forgejo-ci-go image the tools are probably pre-baked, so the conditional command -v checks passed and the step ran silently. If so, install-deps does execute (and installs for consumers who lack the tools), and the fix is complete. But that's the inverse interpretation of what the diagnostic claims ("do not execute"), and the two can't both be true. Worth confirming which, because:

  • If install-deps no-op'd (deps present) → the diagnostic slightly over-stated ("skipped" vs "ran silently"), the fix is complete, and #16's writeup should be refined to say only normalize-version was genuinely skipped.
  • If install-deps was skipped (same runner behavior) → the fix is incomplete for the external-consumer case, and install-deps should be inlined into the run step too (symmetric with normalize-version), or the limitation documented.

This doesn't block the alcatraz dogfood (deps pre-baked either way), so APPROVED for that. But since install-deps is explicitly "preserved for external consumers," its execution path is exactly the thing the preservation depends on — a one-line check of the task-12899 log (did the command -v yq lines appear, or was the step truly absent?) settles it. Same filed-root-cause-is-hypothesis discipline: the normalize-version skip is confirmed; the install-deps skip is the part still carrying an ambiguous-evidence interpretation.

Scope discipline otherwise spot-on (no consumer-side @v0.2.0@v0.3.1 bump here; act_runner root-cause deferred upstream). → your self-merge; worth resolving the install-deps question before v0.3.1 ships so the external-consumer story is solid.

## ✅ APPROVED — #16 inline-collapse fix (v0.3.1 slice 1/3) · one diagnostic-vs-fix question The fix correctly addresses the confirmed failure, and the empty-version guard is a real improvement. One question about whether it fully covers the external-consumer case — worth resolving since that's the case install-deps exists for. ### The fix — correct for the confirmed failure The normalize-version logic is preserved verbatim inline (`v="${v#release-prep/}"; v="${v#v}"`), now in the same step as `draft-release.sh`, so there's no cross-step `$GITHUB_OUTPUT` dependency to come back empty. That directly addresses the confirmed root cause (the `steps.version.outputs.version` reference resolving empty → `--version ""` → bail). And the **empty-version guard is a strict improvement** — an empty `$v` now fails loud with an actionable `::error::` naming the expected forms, instead of the old silent bail. Good defensive add. Canonical probe: 233 ok, 2 not-ok = pre-existing #18 only. ### ⚑ Diagnostic-vs-fix asymmetry: install-deps The #16 diagnostic states *"`install deps` + `normalize version` steps DO NOT execute — the runner jumps `checkout` → `run draft-release.sh` directly."* So the diagnosis names **both** steps as skipped. But this fix only inline-collapses `normalize version`; `install deps` stays a separate step (correctly preserved for external consumers, per your scope note). The tension: if the act_runner genuinely skips steps in that position — as the diagnostic asserts — then **`install deps` is still skipped**, and an external consumer without pre-baked `yq`/`jq`/`curl` would still fail (deps missing), just at a different point than the empty-version bail. I think the likely resolution is that `install deps`'s "no output" was a **no-op, not a skip** — on the alcatraz `forgejo-ci-go` image the tools are probably pre-baked, so the conditional `command -v` checks passed and the step ran silently. If so, install-deps *does* execute (and installs for consumers who lack the tools), and the fix is complete. But that's the inverse interpretation of what the diagnostic claims ("do not execute"), and the two can't both be true. **Worth confirming which**, because: - If install-deps **no-op'd** (deps present) → the diagnostic slightly over-stated ("skipped" vs "ran silently"), the fix is complete, and #16's writeup should be refined to say only normalize-version was genuinely skipped. - If install-deps **was skipped** (same runner behavior) → the fix is incomplete for the external-consumer case, and install-deps should be inlined into the run step too (symmetric with normalize-version), or the limitation documented. This doesn't block the alcatraz dogfood (deps pre-baked either way), so APPROVED for that. But since install-deps is explicitly "preserved for external consumers," its execution path is exactly the thing the preservation depends on — a one-line check of the task-12899 log (did the `command -v yq` lines appear, or was the step truly absent?) settles it. Same filed-root-cause-is-hypothesis discipline: the normalize-version skip is confirmed; the install-deps skip is the part still carrying an ambiguous-evidence interpretation. Scope discipline otherwise spot-on (no consumer-side `@v0.2.0`→`@v0.3.1` bump here; act_runner root-cause deferred upstream). → your self-merge; worth resolving the install-deps question before v0.3.1 ships so the external-consumer story is solid.
Sign in to join this conversation.
No description provided.