Repository navigation
ci(release): run release.yml's tools from the workflow's own revision, not the caller's lock - #42
Merged
Merged
Conversation
…, not the caller's lock
release.yml passed flags its own revision's CLI accepts, but ran the CLI from the caller's dev shell, built from whatever revision the caller's flake.lock pins. A caller on release.yml@main with an older lock failed in the version job: `pr --output` printed help and exited 1 (stryker-js-effect Release run 37833688902).
Each job now builds github:${job.workflow_repository}/${job.workflow_sha}#release-tools and runs those binaries by path inside the caller's dev shell, which still supplies pnpm, git and the sandbox. The e2e runs the version job's steps through a nix stub whose dev shell holds an older revision's tools
… tool build once Review of #42 found the harness's nix build stub answered any argument list whose last word was the tools ref, so dropping --print-out-paths from release.yml still passed while a real runner would set RELEASE_TOOLS=/bin. The stub now accepts only `nix build --no-link --print-out-paths <ref>` and also models .#workspace-tarballs. Only the version job ran through the harness. The release phase now runs release.yml's plan and release jobs step by step and asserts the plan job's phase output and every release step, so a bare tool name in either job fails the suite. The build and pack steps are YAML anchors, defined once and aliased in the other jobs; a composite action would resolve against the caller's checkout and bring the skew back. The README quick start now says where WORKFLOW_REPOSITORY and WORKFLOW_SHA come from and keeps the empty-context guards
Contributor
There was a problem hiding this comment.
Verified: tools built from github:${job.workflow_repository}/${job.workflow_sha} with :? guards; fleet runner 2.337.0; e2e fails on 2a4722c (caller-lock tool ran); fresh review approve-with-fixes, F1-F4 fixed with sabotage probes caught; real flake build of b5700bf lists --output; CI 5/5; hunt grep clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
stryker-js-effect Release run 37833688902 failed in "version · open release PR". The step
nix develop --command github-release-management pr --output "$GITHUB_OUTPUT"printed the CLI help, logged##[error]Help requestedand exited 1.release.ymland the tools it runs came from two different revisions:uses: systemfsoftware/pnpm-release-management/.github/workflows/release.yml@mainresolved to 2a4722c, whose pr step passes--output(fix(release): dispatch the release PR's CI on the branch pr reports, and fail when it cannot #41).nix develop --command github-release-managementran therelease-toolsin the caller's dev shell, which came from the caller'sflake.lockpin, 0449f15. That revision predates--output.So any change to the flags
release.ymlpasses broke every caller until each one bumped its lock.Change
Each job in
release.ymlnow starts with one step. It is written once, as the YAML anchor&release-toolsin theplanjob, and theversionandreleasejobs reuse it as*release-tools. GitHub Actions has supported YAML anchors since 2025-09-18. The.#workspace-tarballsstep is shared the same way.Every tool call becomes
nix develop --command "$RELEASE_TOOLS/<app>" …:pnpm lsandpnpm config get), git, the sandbox andSANDBOX_PNPM_STORE.job.workflow_sha is emptybefore any tool runs. It doesn't fall back to the caller's copy.Why
job.workflow_shaGitHub's contexts reference,
jobcontext defines these properties:job.workflow_ref: "The full ref of the workflow file that defines the current job … For jobs defined in a reusable workflow, this refers to the reusable workflow file."job.workflow_sha: "The commit SHA of the workflow file that defines the current job."job.workflow_repository: "Theowner/repoof the repository containing the workflow file that defines the current job."The same page shows a reusable workflow checking out its own source with
repository: ${{ job.workflow_repository }}andref: ${{ job.workflow_sha }}.These are the only values that identify the called workflow's own revision from inside it. The alternatives don't work:
github.workflow_sha/github.workflow_ref: these describe the caller's top-level workflow (for exampleoctocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch), not the called one.github.sha: this is the caller's commit.uses:at "our own ref": this can't be written. Auses:ref is static text and can't be an expression, and a relative./path in a reusable workflow resolves against the caller's checkout.tools-refinput: this moves the skew onto the caller instead of removing it.job.workflow_shais a commit SHA, not a branch name, so the tools are the exact commit@main(or@v1, or@<sha>) resolved to for this run, even ifmainmoves during the run.nix build github:<repo>/<sha>#release-toolsbuilds the same output the dev shells already take aspackages.<system>.release-tools.Runner support: github.com provides the context. The runner side landed in actions/runner#4335 (first released in v2.334.0). The docs say GitHub Enterprise Server doesn't have it; the README now says so, and such a runner fails at the guard above.
Test
The e2e (
@systemfsoftware/e2e, part ofpnpm test) now runs all three jobs (plan,versionandrelease) step by step, asrelease.ymlwrites them. The old harness rewrotenix develop --command <app>to the image's binaries, which made the resolution invisible. Now:nixon the runner PATH is a stub that models the two verbsrelease.ymluses:develop --commandenters a "caller dev shell" whoserelease-toolsare an older revision's (0449f15): each one exits 1, naming itself.buildaccepts onlynix build --no-link --print-out-paths <ref>, the shaperelease.ymluses. It resolvesgithub:systemfsoftware/pnpm-release-management/<workflow sha>#release-toolsto the binaries built from this checkout, and.#workspace-tarballsto the tarballs the test packed.job.workflow_repositoryandjob.workflow_shaare resolved like the runner resolves them.GITHUB_ENVcarries over from one step to the next.tool()still finds each step'senv.planjob and asserts itsphase=releaseoutput. It then runs thereleasejob and checks that every step ran (build, pack, capture, tag, GitHub Releases) and that each exited 0. The existing assertions on the pushed tags and the release notes follow.Failing before: I ran this branch's harness and test against
release.ymlfrom main 2a4722c, in a throwaway checkout (pnpm --filter @systemfsoftware/e2e test, exit 1). It fails in the version phase (pipeline.integration.test.ts:279):On this branch (b5700bf):
pnpm --filter @systemfsoftware/e2e testexits 0 (Test Files 1 passed (1),Tests 1 passed (1), 299.12s).Sabotage checks (throwaway edits to
release.yml, reverted):--print-out-pathsfrom the build step. Before the review fix, the stub still passed this, while a real runner would setRELEASE_TOOLS=/bin. Now the suite fails:nix develop --command github-release-management tag …in thereleasejob. Before the review fix, no test ran that job. Now the suite fails (pipeline.integration.test.ts:419):Review
A fresh-context correctness review returned
VERDICT: approve-with-fixes. Its findings and how each was handled:nix buildstub ignored its flags, so dropping--print-out-pathspassed. The stub now requires the exact build shape.planandreleaseweren't exercised by any test. The stub now models.#workspace-tarballs, and the release phase runs both jobs../.github/actions/…resolves against the caller's checkout.WORKFLOW_REPOSITORY/WORKFLOW_SHAwithout introducing them. It now says where they come from and keeps the:?guards.nix build github:makes an unauthenticated API call. GitHub-hosted runners get a fresh IP per job, so it only matters for self-hosted pools behind one egress IP.Consumers' dev shells
The flake outputs are unchanged, so a consumer's dev shell keeps its locked
release-toolsfor local use.release.ymlno longer reads them; the README's distribution and CI sections now say so.Not covered here
.#workspace-tarballsis still built by the caller's flake. That's packing the caller's workspace, not running a tool.pnpm(pnpm ls -r --json,pnpm config get). A future tool revision that depends on a newer pnpm CLI could still skew against an old dev shell, one layer down.changeset-check.ymlhas the same class of skew: withdevshell: trueit runschangeset-managementfrom the caller's dev shell, and otherwise it checks outtools-ref(defaultmain). This PR leaves it alone.No changeset: only the workflow, the e2e and the README change.