ci(lock): gate every commit on its own actions.lock self-consistency - #821
Merged
Merged
Conversation
A caller writes `uses: hyperpolymath/standards/.github/workflows/x.yml@<sha>`. GitHub resolves that at workflow STARTUP and validates the callee against the CALLEE repo's own `.github/workflows/actions.lock` AS IT EXISTS AT THAT SHA. If a reusable here names an action ref the lock at that SHA does not key, every caller pinned there dies before a single job exists. That death is nearly invisible. The run's conclusion is `failure`, NOT `startup_failure`; it has ZERO jobs; its `name` equals its `path`; and neither REST nor GraphQL carries a reason -- only the run page states one. A required context whose workflow dies this way never reports at all, so the gate reads as ABSENT rather than failing and the branch looks clean. That is how this class stays unnoticed while it spreads. Capability is therefore NOT monotonic in time, and it OSCILLATES. Measured across the 84 commits da2c748..317101e: 43 POISON / 41 SELF-CONSISTENT, alternating, because Dependabot bumps a `uses:` inside a reusable without regenerating the lock in the same commit. "Pin to HEAD" is the wrong reflex -- the current tip is itself poison. This gate makes `main` safe to pin to by construction rather than by luck. Contents: scripts/lock-selfcheck.sh Answers one question: is a given commit safe to pin a caller to? Reads git objects only (`git show`), never checks out, fetches or writes, so it is safe against a checkout shared with a live writer. Exit 0 all examined SHAs self-validate, 1 any POISON, 2 misuse. It distinguishes three states that are easy to conflate: POISON (the workflow is keyed but this ref is not listed -- startup-fatal), ABSENT (the workflow has no lock entry at all -- untidy, not fatal), and NO-LOCK (the commit pre-dates the lock regime -- not a defect). Only POISON fails. It compares case-insensitively, because the lockfile lowercases the owner/repo it keys while workflows carry upstream casing, and it normalises subpaths, because `github/codeql-action/upload-sarif@SHA` is keyed as `github/codeql-action@SHA`. Both of those produced false verdicts before they were fixed. .github/workflows/lock-selfcheck.yml Runs it against the commit under test. Deliberately carries NO `paths:` filter: this check is meant to be REQUIRED, and a required check that filters itself off does not report as passing -- it does not report at all, leaving the PR blocked on a context that can never arrive. Also repins three stale lock entries this check flags. codeql-reusable.yml, hypatia-scan-reusable.yml and scorecard-reusable.yml all use github/codeql-action@b96794f0 (v4.38.0, 2026-09-09) while the lock still keyed @cdf488f5 (v4.37.9, 2026-08-26). The workflows are newer and the lock is stale, so the lock is what moves: @cdf488f5 appears 6 times in the lock and is used by ZERO workflow files. The three other stale entries are left untouched on purpose -- they belong to workflows that call no codeql, so widening the diff would buy nothing. KNOWN RED, AND CORRECTLY SO: this gate still fails on a fourth ref. governance-reusable.yml uses denoland/setup-deno@22d081ff, which is both unkeyed and a banned runtime. It is not keyed here deliberately -- keying it would make a banned runtime a required lock key for every caller in the estate. Nor is the job deleted: its name is a required context estate-wide. The step is load-bearing (line 383 runs `deno run` against scripts/check-ts-allowlist.deno.js, which is 360 lines of compiled output from an AffineScript source), so removing it is a compiler-target change, not a text edit. That is task #15. Until #15 lands, this gate reports the deno ref red, and that red is accurate: a caller pinned at such a commit really does die. Landing this advisory-first and marking it required after #15 is a reasonable sequence; so is fixing #15 first. That ordering is an owner call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgqXnnNWBkiKMyUeLqzcuN
Contributor
|
Warning Review limit reachedNext included review available in 54 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
hyperpolymath
enabled auto-merge (squash)
September 17, 2026 19:55
|
hyperpolymath
added a commit
that referenced
this pull request
Sep 17, 2026
## What `main` is **POISON** by this repo's own `scripts/lock-selfcheck.sh`, added in #821. The gate went red on its own merge commit, and the cause predates it: ``` .github/workflows/governance-reusable.yml uses denoland/setup-deno@22d081f lock keys for this workflow: actions/cache … actions/checkout … editorconfig-checker/… erlef/setup-beam … ``` The ref is used but not keyed. It arrived with `cdec573` (#818, the 2.0.2 release) and no lock regeneration rode along. ## Why it matters GitHub resolves a reusable's action refs against the **callee** repo's `actions.lock` **at the pinned SHA**. An unkeyed ref kills every caller at workflow startup with **zero jobs**, conclusion `failure` (not `startup_failure`), and no reason carried in either REST or GraphQL — the run page is the only place that states one. A required context that dies this way never reports at all, so the gate reads as **absent** rather than failing and the branch looks clean. `main` is not currently a safe thing to pin to. ## Why this fix is deterministic The lockfile validator requires **key-SHA == the digest written inline in the workflow**, so the correct entry has exactly one possible value and needs no resolution, network, or judgement. This reproduces what `gh actions-lock` + `scripts/relock-sha-keys.sh` would key, with no other entry moved. ## Verification ``` scripts/lock-selfcheck.sh 176d1f7 -> VERDICT: POISON — 1 ref used but NOT keyed (rc=1) scripts/lock-selfcheck.sh 46ef434 -> VERDICT: SELF-CONSISTENT (rc=0) ``` Diff is one added line in `.github/workflows/actions.lock` — the key, inserted in the file's alphabetical convention. ## Not fixed here `lock-selfcheck.sh` also reports **6 workflows with no lock entry at all** (`apply-workflow-pins.yml`, `pages-archive.yml`, `propagate-hooks.yml`, `security-gate-pr-target.yml`, `settings-drift-detect.yml`, `tag-ruleset-canon.yml` — 18 refs). The gate states this is a separate question from the verdict and does not count it. Left alone deliberately: an empty `[]` entry is not obviously the intended answer for a workflow that may not be a reusable. ## Related `uses ⊆ actions.lock` is red on `main` with the same root cause (it shells out to `update-actions-lock.sh --verify-local`). Expect it to clear with this change; if it does not, it is a second defect, not the same one.
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.



A caller writes
uses: hyperpolymath/standards/.github/workflows/x.yml@<sha>. GitHub resolves that at workflow STARTUP and validates the callee against the CALLEE repo's own.github/workflows/actions.lockAS IT EXISTS AT THAT SHA. If a reusable here names an action ref the lock at that SHA does not key, every caller pinned there dies before a single job exists.That death is nearly invisible. The run's conclusion is
failure, NOTstartup_failure; it has ZERO jobs; itsnameequals itspath; and neither REST nor GraphQL carries a reason -- only the run page states one. A required context whose workflow dies this way never reports at all, so the gate reads as ABSENT rather than failing and the branch looks clean. That is how this class stays unnoticed while it spreads.Capability is therefore NOT monotonic in time, and it OSCILLATES. Measured across the 84 commits da2c748..317101e: 43 POISON / 41 SELF-CONSISTENT, alternating, because Dependabot bumps a
uses:inside a reusable without regenerating the lock in the same commit. "Pin to HEAD" is the wrong reflex -- the current tip is itself poison. This gate makesmainsafe to pin to by construction rather than by luck.Contents:
scripts/lock-selfcheck.sh
Answers one question: is a given commit safe to pin a caller to? Reads
git objects only (
git show), never checks out, fetches or writes, so itis safe against a checkout shared with a live writer. Exit 0 all examined
SHAs self-validate, 1 any POISON, 2 misuse.
.github/workflows/lock-selfcheck.yml
Runs it against the commit under test. Deliberately carries NO
paths:filter: this check is meant to be REQUIRED, and a required check that
filters itself off does not report as passing -- it does not report at
all, leaving the PR blocked on a context that can never arrive.
Also repins three stale lock entries this check flags. codeql-reusable.yml, hypatia-scan-reusable.yml and scorecard-reusable.yml all use github/codeql-action@b96794f0 (v4.38.0, 2026-09-09) while the lock still keyed @cdf488f5 (v4.37.9, 2026-08-26). The workflows are newer and the lock is stale, so the lock is what moves: @cdf488f5 appears 6 times in the lock and is used by ZERO workflow files. The three other stale entries are left untouched on purpose -- they belong to workflows that call no codeql, so widening the diff would buy nothing.
KNOWN RED, AND CORRECTLY SO: this gate still fails on a fourth ref. governance-reusable.yml uses denoland/setup-deno@22d081ff, which is both unkeyed and a banned runtime. It is not keyed here deliberately -- keying it would make a banned runtime a required lock key for every caller in the estate. Nor is the job deleted: its name is a required context estate-wide. The step is load-bearing (line 383 runs
deno runagainstscripts/check-ts-allowlist.deno.js, which is 360 lines of compiled output from an AffineScript source), so removing it is a compiler-target change, not a text edit. That is task #15. Until #15 lands, this gate reports the deno ref red, and that red is accurate: a caller pinned at such a commit really does die.
Landing this advisory-first and marking it required after #15 is a reasonable sequence; so is fixing #15 first. That ordering is an owner call.
Claude-Session: https://claude.ai/code/session_01WgqXnnNWBkiKMyUeLqzcuN
Summary
Closes #
Type of change
How has this been verified?
Checklist
git commit -S).SPDX-License-Identifier(code/configMPL-2.0,prose
CC-BY-SA-4.0); I did not relicense existing files.Notes for reviewers