fix(ci): pin the breadcrumb job's checkout to the validated commit - #137
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes the breadcrumb job in the nightly release workflow so it checks out (and verifies) the exact commit that Validate ran on when triggered via workflow_run, preventing “fail-open” checkouts of the default branch tip.
Changes:
- Pin
actions/checkoutinbreadcrumbtogithub.event.workflow_run.head_sha. - Add a guard step that fails the job if
head_shais missing or if the checked-outHEADdoes not match the validated commit.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
thecodedrift
added a commit
that referenced
this pull request
Aug 21, 2026
The nightly has failed on every push since #132 merged — twice, on f5b3797 and 20f18f6 — with: Failed to find where HEAD diverged from "main". Does "main" exist and it's synced with remote? `changeset status` resolves baseBranch from .changeset/config.json and shells out to `git merge-base main HEAD`. Under the old `push` trigger the checkout took no `ref:`, so it checked out refs/heads/main and created a local `main` branch as a side effect. That side effect, not anything deliberate, is what made the command work; three nightlies published on top of it. `workflow_run` requires an explicit `ref:` — an empty one silently falls back to the default branch — but checking out a bare sha lands in detached HEAD with no branches, so `main` stopped resolving. Reproduced outside CI with `git fetch --depth=1 origin <sha>` + `git checkout --detach FETCH_HEAD`, which yields exactly `fatal: Not a valid object name main`. Pointing `main` at HEAD is not an approximation of the previous behavior, it is that behavior: on a push to main the checked-out commit and the branch were the same commit, so merge-base returned HEAD then too. getChangedPackagesSinceRef was already a no-op here and stays one — the `releases` array this step is read for comes from the .changeset/*.md files, not from a git diff. `fetch-depth: 0` is the obvious wrong fix and the comment says so: a bare `main` does not resolve through a remote-tracking ref, so it would buy a full clone on every run and still fail. This is the failure mode #132 called out as structurally unverifiable before merge — workflow_run loads the workflow from the default branch, so no pull request could exercise it. It failed closed rather than publishing something unvalidated. The breadcrumb job's unpinned checkout is a separate defect, fixed in #137. Refs #127 Refs #131 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwc9FFroR3mTZ4hLiSkkX3
#133 and #132 crossed. The nightly moved to `workflow_run` in #132, where every checkout must carry `ref: github.event.workflow_run.head_sha`; the `breadcrumb` job added by #133 was written against the `push` trigger and merged without it. Under `workflow_run` that is not an error. `github.sha` is the default branch tip at event time, so the job checks out whatever `main` had moved to and succeeds — running a copy of nightly-breadcrumb.cjs that Validate never saw, while announcing a nightly built from a different commit. An empty `ref:` fails the same way, silently, which is why the other two jobs assert HEAD rather than trusting the checkout. Cosmetic output does not lower the bar: it is the same fail-open shape, and it reads as a normal green run. The job now pins the ref and asserts HEAD against it, matching gate and publish line for line. Nothing else moves. `breadcrumb` still reaches gate 0 transitively — a skipped `gate` skips `publish`, which skips this — so it restates none of those four conditions, and it consumes the publish job's stamped version rather than re-deriving a short sha. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwc9FFroR3mTZ4hLiSkkX3
thecodedrift
force-pushed
the
fix/breadcrumb-workflow-run-ref
branch
from
August 21, 2026 17:33
d8fbe96 to
17b2206
Compare
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.
#132 and #133 crossed. #132 moved the nightly to
workflow_run, where every checkout must carryref: ${{ github.event.workflow_run.head_sha }}; thebreadcrumbjob #133 added was written against thepushtrigger and merged without it.On
mainright now,gateandpublishpin the ref and assert HEAD;breadcrumbdoes neither.Why this is not cosmetic
Under
workflow_run,github.shais the default branch tip at event time. So the job checks out whatevermainhas moved to and succeeds — running a copy ofnightly-breadcrumb.cjsthatValidatenever saw, while announcing a nightly built from a different commit. An emptyref:fails the same way:actions/checkoutfalls back to the default branch and exits 0. That is exactly the fail-open the two other jobs assert against, and it reads as a normal green run.The job now pins the ref and asserts HEAD against it, matching
gateandpublishline for line (including the "payload carried no head_sha" branch).What deliberately does not change
breadcrumbneedspublish, which needsgateand isif:-gated on its output — a skippedgateskipspublish, which skips this. Verified against the parsed workflow rather than assumed.needs.publish.outputs.version, the once-stamped version, and parses the build time and commit back out of it. Nothing here abbreviates a sha, so the prefix-comparison change in f5b3797 has nothing to interact with.pull-requests: write,contents: read, noid-token.Verification
pnpm lintexit 0 ·node --test .github/scripts/*.test.cjs178 pass, 0 fail ·pnpm openspec validate --all --strict24 passed, 0 failed. Parsed the workflow and confirmed all three jobs now carry the sameref:, and that theneeds/ifchain reaches gate 0 transitively.The first real proof is the first qualifying push after this merges — GitHub loads a
workflow_runworkflow from the default branch, so this cannot be exercised on its own PR.Refs #128
Refs #127