Skip to content

fix: correct the release guards and let callers pin the image tag - #20

Open
roxdanila wants to merge 6 commits into
masterfrom
fix/release-guards
Open

roxdanila wants to merge 6 commits into
masterfrom
fix/release-guards

Conversation

@roxdanila

@roxdanila roxdanila commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Four fixes to the guards these workflows provide, found while setting up CI in nexus-mutual-mcp.

check-workflow-status matched every workflow

The script passed workflow_id to listWorkflowRunsForRepo. That endpoint takes no such parameter, so GitHub returned runs for every workflow in the repo. Measured against membership-api:

repos/.../actions/runs?workflow_id=release.yml
  merge-to-dev-or-hotfix.yml: 3   pull-request.yml: 13   release.yml: 3
  dependabot-updates: 80          code-security-risk-assessment: 1

repos/.../actions/workflows/release.yml/runs
  release.yml: 7

With fail-if-exists: true, a release failed whenever anything in the repo sat in the requested status. Ten repos call this workflow.

It now calls listWorkflowRuns, which scopes to workflow_id, and fails with a clear message when the name is missing. Inputs reach the script through the environment.

A single status matched too few runs

workflow-status: non-terminal covers every run that has yet to finish. A run blocked by a concurrency group reports queued or pending depending on the trigger, so one status covers part of the range. It queries each of queued, in_progress, waiting, pending and requested in turn and sums total_count, which spans every page.

created-within-minutes bounds the lookback, so a run stuck in a non-terminal state clears and releases resume. It defaults to 0, which considers every run.

Both inputs are additive. Existing callers keep their behaviour.

An unrecognised status passed the guard

The API answers an unknown status with an empty list rather than an error, so workflow-status: in_progres reported nothing and the release went ahead. Verified against a live repo: status=bogus_status returns total_count: 0. The input is now checked against the documented values.

check-tag-version-equality passed unconditionally

The checkout set fetch-tags: true and left fetch-depth at 1. git describe --tags --abbrev=0 walks back from HEAD, so a shallow clone stopped short, the step fell back to v0.0.0, and the package version always differed.

build-image and tag-image resolved a moving tag

Both compute <version>-<sha> from a checkout of ref. A caller passing a branch name gets the tip at the moment each job starts, so a merge landing mid-run shifts the tag between them and docker pull fails.

  • bump.yml reports commit_sha, so callers pin the build to the commit the bump created.
  • tag-image.yml accepts source-tag, so callers pass needs.build-image.outputs.image-tag straight through.

Verified

The rewritten script ran against mocked responses covering: workflow_id scoping and the repo-level endpoint staying unused, total_count past the first page, non-terminal across all five states, finished runs going unqueried, an unknown status failing before any request, the window on and off, the 404 path, fail-if-exists: false, and a custom error-message.

Each of the five non-terminal statuses was also checked against the live API, confirming GitHub accepts them as status filters.

All nine other callers pass merge-to-dev-or-hotfix.yml, and that file exists in each of their repos, so the new 404 path affects none of them.

These now run in CI. test/check-workflow-status.test.js reads the script block out of the workflow and exercises it against a stubbed octokit, and pull-request.yml runs it on every PR. That gives this repo its first automated check.

actionlint stays for #21. It reports findings in workflows this branch leaves alone.

The live-API checks above sit outside what a stubbed harness can prove, so they stay manual.

Every other caller passes workflow-status: in_progress, which the new validation accepts.

Left open

#17 needs a decision before it moves. Resetting the bump range means tagging at bump time, which check-tag-version-equality and git-tag-github-release both treat as a failure today. nexus-mutual-mcp works around it locally for now.

Closes #16
Closes #18
Closes #19

The script passed workflow_id to listWorkflowRunsForRepo. That endpoint has no
such parameter, so GitHub returned runs for every workflow in the repo. With
fail-if-exists set, a release failed whenever anything in the repo sat in the
requested status, a dependabot update included. Call listWorkflowRuns, which
scopes to workflow_id, and fail clearly when the name does not resolve.

Add non-terminal as a workflow-status, matching every run that has yet to
finish. A run blocked by a concurrency group reports queued or pending
depending on the trigger, so naming a single status leaves gaps.

Add created-within-minutes to bound the lookback, so a run stuck in a
non-terminal state stops blocking releases. It defaults to 0, which considers
every run.

Both inputs are additive and existing callers keep their behaviour. Inputs now
reach the script through the environment, keeping them out of the source.
The checkout set fetch-tags and left fetch-depth at 1. git describe walks back
from HEAD, so on a shallow clone it found nothing, the step fell back to
v0.0.0, and the package version always differed. The guard passed whatever the
real state was.
build-image and tag-image resolve <version>-<sha> from a checkout of ref. A
caller passing a branch name gets the tip at the moment each job starts, so a
merge landing mid-run shifts the tag between them.

Report the commit this job pushed, so callers pin both to it.
Reading version and sha from ref resolves against the branch tip, which moves.
A caller that already knows what build-image pushed passes it in as source-tag
and skips the lookup.
Tallying a single page missed runs beyond the first hundred, which is the
stuck-run case the lookback window exists for. Query each non-terminal status
in turn and sum total_count, which covers every page.

The API returns an empty list for a status it fails to recognise, so a typo in
workflow-status passed the guard silently. Accept only documented values.

Drop fetch-tags from check-tag-version-equality, since depth 0 brings tags.

Make ref optional in tag-image and skip the checkout when source-tag is set.
@roxdanila roxdanila changed the title fix: scope the workflow status check, and pin image tags to the bump commit fix: correct the release guards and let callers pin the image tag Aug 7, 2026
Twelve repos depend on these workflows and nothing ran against them before they
reached master. The status check carries the only real logic here, so read its
script out of the workflow and run it against a stubbed octokit.

15 cases: workflow_id scoping, total_count past one page, non-terminal across
all five states, an unrecognised status failing before any request, the
lookback window, the 404 path, fail-if-exists, and error-message.

A pull-request workflow runs them. actionlint stays for #21, since it reports
findings across workflows this branch leaves alone.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant