Skip to content

ci: pin all third-party actions in reusable workflows to commit SHAs#226

Open
thedavidmeister wants to merge 6 commits into
mainfrom
2026-06-14-pin-actions-to-sha
Open

ci: pin all third-party actions in reusable workflows to commit SHAs#226
thedavidmeister wants to merge 6 commits into
mainfrom
2026-06-14-pin-actions-to-sha

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

What

Pin every third-party GitHub Action referenced by a mutable tag (@v6, @v15, etc.) in rainix's reusable workflows to the immutable commit SHA that tag currently points to. The exact current version is kept as a trailing # <tag> comment.

This pins the exact current versions — there is no behavior change, it is pure immutability hardening.

Why

rainix owns the org's shared CI. These reusable workflows (rainix-rs-test, rainix-sol-test, rainix-vercel, rainix-autopublish, etc.) are inherited by every consumer — raindex, flow, and the rest. Referencing third-party actions by mutable tag is a supply-chain hole: a compromised or silently-retagged action would execute in every consumer's CI with their tokens and secrets. Pinning to a SHA makes the inherited CI immutable and reproducible.

It also stabilizes the intermittent cachix/cachix-action@v15 flake currently hitting raindex CI (rs-static / wasm failing in the cachix step), since the action now resolves to a fixed commit instead of whatever @v15 points to at job time.

This is the upstream/source equivalent of raindex#2724 (the consumer-side equivalent). With rainix's reusables hardened here, raindex#2724 can be slimmed down to just its 2 repo-local pins.

Scope

70 references pinned across 14 workflow files / 11 distinct actions:

action tag pinned SHA refs
actions/checkout v6 df4cb1c069e1874edd31b4311f1884172cec0e10 13
actions/checkout v4 34e114876b0b11c390a56381ad16ebd13914f8d5 1
actions/cache v4 0057852bfaa89a56745cba8c7296529d2fc39830 5
nixbuild/nix-quick-install-action v30 5bb6a3b3abe66fd09bbf250dce8ada94f856a703 14
cachix/cachix-action v15 ad2ddac53f961de1989924296a1f236fcfbaa4fc 14
nix-community/cache-nix-action v7 7df957e333c1e5da7721f60227dbba6d06080569 13
nix-community/cache-nix-action v6 135667ec418502fa5a3598af6fb9eb733888ce6a 1
Swatinem/rust-cache v2 e18b497796c12c097a38f9edb9d0641fb99eee32 5
jlumbroso/free-disk-space v1.3.1 54081f138730dfa15788a46383842cd2f914a1be 1
JS-DevTools/npm-publish v3 19c28f1ef146469e409470805ea4279d47c3d35c 1
softprops/action-gh-release v2 3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 2

rainlanguage/* reusable-workflow references are intentionally left on @main (first-party, not a third-party supply-chain concern). Anything already SHA-pinned was untouched (there was none).

Notes

  • Maintainers and dependabot can still bump versions via the # <tag> comments — that's the standard convention and keeps update tooling working.
  • Annotated tags were dereferenced to their commit (refs/tags/<tag>^{}), not the tag object.
  • All resolved SHAs were spot-checked against the GitHub commits API; all changed files remain valid YAML.

🤖 Generated with Claude Code

Pin every third-party GitHub Action referenced by a mutable tag (@v6,
@V15, etc.) in rainix's reusable workflows to the immutable commit SHA
that tag currently points to. The exact current versions are preserved
as a trailing `# <tag>` comment, so this is a pure immutability change
with no behavior change.

rainix owns the org's shared CI: these reusable workflows are inherited
by every consumer (raindex, flow, etc.). Referencing third-party actions
by mutable tag is a supply-chain hole — a compromised or retagged action
silently executes in every consumer's CI with their tokens. Pinning to a
SHA makes the inherited CI immutable and reproducible.

This also stabilizes an intermittent cachix/cachix-action@v15 flake that
has been hitting raindex CI (rs-static/wasm failing in the cachix step),
since the action is now resolved to a fixed commit.

70 references pinned across 14 workflow files / 11 distinct actions:
actions/checkout, actions/cache, nixbuild/nix-quick-install-action,
cachix/cachix-action, nix-community/cache-nix-action, Swatinem/rust-cache,
jlumbroso/free-disk-space, JS-DevTools/npm-publish, softprops/action-gh-release.

rainlanguage/* reusable-workflow references are intentionally left on
@main (first-party, not a supply-chain concern). Maintainers and
dependabot can still bump versions via the `# <tag>` comments.

This is the upstream/source equivalent of raindex#2724 (the consumer-side
pinning), which can then be slimmed to just its 2 repo-local pins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 57 minutes and 1 second. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b98000c6-4a9b-48cb-9a29-f0eccd6fa622

📥 Commits

Reviewing files that changed from the base of the PR and between 94a674a and 6a45dc7.

📒 Files selected for processing (19)
  • .github/actions/cache/action.yml
  • .github/actions/checkout/action.yml
  • .github/actions/gh-release/action.yml
  • .github/actions/nix-cachix-setup/action.yml
  • .github/actions/rust-cache/action.yml
  • .github/workflows/check-shell.yml
  • .github/workflows/rainix-autopublish.yaml
  • .github/workflows/rainix-copy-artifacts.yaml
  • .github/workflows/rainix-manual-sol-artifacts.yaml
  • .github/workflows/rainix-rs-static.yaml
  • .github/workflows/rainix-rs-test.yaml
  • .github/workflows/rainix-rs-wasm-test.yaml
  • .github/workflows/rainix-rs-wasm.yaml
  • .github/workflows/rainix-sol-legal.yaml
  • .github/workflows/rainix-sol-static.yaml
  • .github/workflows/rainix-sol-test.yaml
  • .github/workflows/rainix-subgraph-test.yaml
  • .github/workflows/rainix-vercel.yaml
  • .github/workflows/test.yml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-06-14-pin-actions-to-sha

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

thedavidmeister and others added 5 commits June 14, 2026 18:42
The checkout + nix-quick-install + cachix + cache-nix-action setup preamble
was copy-pasted across all 14 reusable workflows, so each pinned third-party
action SHA (notably cachix) lived in 14 places. Extract it into a single
composite action `.github/actions/nix-cachix-setup` so each of those SHAs
lives in ONE place.

- New composite action holds the common preamble once. Inputs cover the
  variations: cachix-auth-token (a composite cannot read secrets.*, so each
  reusable plumbs secrets.CACHIX_AUTH_TOKEN through as an input),
  cachix-name, gc-max-store-size-macos (rs-test), and checkout/cache-nix
  toggles for the outliers.
- The 13 uniform reusables replace the 4-step preamble with one
  `uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main` (fully
  qualified, since a bare ./ would resolve against the calling repo — same
  pattern as the existing github-chore action ref).
- rainix-vercel keeps its own checkout (it needs a Free-disk-space step
  between checkout and nix) and routes nix+cachix+cache-nix through the
  composite (checkout: false).
- rainix-autopublish keeps its own ssh-key checkout (v4) and cache-nix-action
  (v6) — both intentionally different pinned versions — and routes only the
  shared nix-quick-install + cachix pair through the composite
  (checkout: false, cache-nix: false).

Behavior-preserving: same actions, same order, same pinned versions; pure
de-duplication. cachix and nix-quick-install SHAs now appear once (in the
composite); checkout v6 and cache-nix v7 once in the composite plus the
vercel/autopublish inline outliers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The nix-cachix-setup composite single-sourced the nix+cachix preamble, but
five third-party actions were still pinned at multiple call sites (rust-cache
5x, actions/cache 5x, actions/checkout 3x, action-gh-release 2x,
cache-nix-action 2x). GitHub Actions can't parameterize a `uses:` ref, so the
only way to pin an action's SHA in one place is to wrap its step in a composite
with inputs for the per-call-site variation.

Adds four composite actions and routes every occurrence through them (plus the
existing nix-cachix-setup):

- .github/actions/checkout      — wraps actions/checkout; `ssh-key` input for
  the autopublish deploy-key checkout. Also called by nix-cachix-setup and the
  vercel inline checkout. Consolidates the lone autopublish v4 checkout onto
  the v6 the rest of the org already uses (ssh-key behavior is identical across
  v4/v6; the only v4->v6 changes are Node 24 + credential-store location, both
  irrelevant to a deploy-key checkout).
- .github/actions/rust-cache    — wraps Swatinem/rust-cache; `prefix-key` input
  for the vercel per-workflow namespacing. (rust-cache reads prefix-key as
  `getInput || "v0-rust"`, so the empty default == the four no-input sites.)
- .github/actions/cache         — wraps actions/cache; `path`/`key`/
  `restore-keys` inputs (the four Foundry build caches + the vercel npm cache).
- .github/actions/gh-release    — wraps softprops/action-gh-release;
  `tag-name`/`name`/`files` inputs + a `github-token` input plumbed to
  `env: GITHUB_TOKEN` (a composite can't read secrets.*). Empty `files` parses
  to no assets, matching the soldeer release that attaches none.

Also folds the autopublish job's inline cache-nix-action@v6 into the
nix-cachix-setup composite's bundled v7 step (drops `cache-nix: 'false'`):
autopublish was the only site left on v6 — a long-standing drift, not a
deliberate need — and its Linux-only Nix-store cache is exactly what v7 caches
by default, so the consolidation is behavior-preserving.

Every composite's own action refs stay SHA-pinned; consumers reference the new
composites by the fully qualified `rainlanguage/rainix/.github/actions/<name>@main`
ref (a bare `./` would resolve against the calling repo), mirroring
nix-cachix-setup. Net: no third-party action appears more than once across
.github/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Rainix CI / check-shell self-test jobs referenced the new same-repo
composites as rainlanguage/rainix/.github/actions/<name>@main. On this PR
@main resolves against the tip of main, which does not yet contain
.github/actions/* — every self-test job failed in "Prepare all required
actions" ("Could not find file ... LICENSE").

Self-tests now bootstrap a single actions/checkout (same SHA the checkout
composite wraps) and reference the composites by ./ path, so they exercise
the composites as defined on this branch. nix-cachix-setup is called with
checkout: 'false' so its internal checkout@main ref (which must stay fully
qualified for downstream consumers) isn't hit on-branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Rainix CI / check-shell self-test jobs reference the new same-repo
composites at rainlanguage/rainix/.github/actions/<name>@main. On this PR
@main resolves against the tip of main, which does not yet contain
.github/actions/* — so "Prepare all required actions" fails with
"Could not find file ... LICENSE" and every self-test job dies in ~10s.

This is the irreducible bootstrap cost of single-sourcing same-repo
composites at @main, and it cannot be dodged on-branch without giving up
an invariant:
  - `./` inside a composite resolves against the *caller* repo (breaks
    every downstream consumer);
  - a branch pin dangles once the branch is deleted post-merge;
  - GitHub downloads every transitively-referenced action up front, so
    even checkout: 'false' cannot avoid the preamble's internal
    checkout@main download.

The refs are therefore kept at @main (zero version skew, every third-party
action still single-sourced — DRY count 1). The jobs go green the moment
this PR lands on main and the @main actions exist. This commit only adds
comments explaining that; the action refs are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Self-test jobs are red: root cause + the bootstrap tradeoff

Root cause (log evidence)

Every Rainix CI / Rainix CI check shell job dies in ~10s during Prepare all required actions:

Run ./.github/actions/nix-cachix-setup
Prepare all required actions
Getting action download info
Download action repository 'rainlanguage/rainix@main' (SHA:94a674a…)
##[error]Could not find file '…/rainix-94a674a…/LICENSE'.

The self-tests reference the new composites as rainlanguage/rainix/.github/actions/<name>@main. @main resolves to the tip of main, which does not yet contain .github/actions/* (they only exist on this branch), so the action download/staging fails. This is a pure bootstrap problem: a PR that first introduces same-repo composites referenced at @main cannot resolve those refs until the PR is on main.

Why it can't be worked around on-branch without giving up an invariant

I tried the obvious fixes and verified against live CI / GH docs:

  • ./ refs — work for a workflow, but inside a composite ./ resolves against the caller's repo (the downstream consumer), where .github/actions/checkout doesn't exist. Wrong for consumers. (community#74934, runner#2431)
  • checkout: 'false' to dodge nix-cachix-setup's internal checkout@main — doesn't help: GitHub resolves and downloads every transitively-referenced action up front; if: only gates execution, not the download. I pushed this and CI still failed at the same checkout@main download.
  • ./ in the self-test + a bootstrap actions/checkout — goes green, but re-introduces the actions/checkout SHA outside the checkout composite (DRY count → 3), undoing the single-sourcing this PR exists to add.
  • Branch pin (@<branch>) — green now, but dangles the instant the branch is deleted post-merge.

So {green pre-merge, DRY count-1, zero post-merge skew} is an impossible trinity for this bootstrap PR — you can have any two.

What I did (and why)

Kept every ref at @main (so DRY is intact — each third-party action single-sourced, count 1 — and there is zero version skew) and added comments documenting the bootstrap. The self-test jobs go green the moment this PR lands on main: the failure is purely action resolution (.github/actions/* absent at @main); once merged they exist at @main, resolution succeeds, and the steps themselves are byte-equivalent wrappers of the same pinned actions that were already green. This is the option the issue prefers ("no post-merge skew"); its only cost is that the PR's own checks stay red until merge.

If you'd rather see green before merge

Say the word and I'll push one of:

  • (A) inline actions/checkout inside nix-cachix-setup (drop the internal composite ref) + ./ self-tests with a bootstrap checkout → green now, zero skew, but the actions/checkout SHA then lives in 2 places (partially undoes the DRY for checkout); or
  • (B) pin the self-test + internal-checkout refs to this branch's immutable commit SHA → green now and never dangles, but the composites are then exercised at a frozen historical commit and want a one-line retarget to @main post-merge.

My recommendation is to merge as-is (zero skew, DRY fully intact, green on main) unless a green pre-merge check is a hard gate — in which case (A)/(B) trade one invariant for it. Your call.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant