ci: own the setup, changelog, and merge-queue steps instead of calling out for them - #1044
Conversation
…g out for them
Pinning our own references was necessary but not sufficient. The policy
resolves actions transitively, and the MetaMask actions we call reach mutable
tags one layer down:
action-checkout-and-setup -> cache/restore@v6, checkout@v7, setup-node@v6,
action-retry-command@v1, cache/save@v6
github-tools/check-changelog -> checkout@v6 x2, setup-node@v6
github-tools/check-skip-merge-queue -> github-script@v9 x2
All three are on their latest releases, so no upgrade reaches them, and they
live in an organization we no longer belong to. A pin one layer deep is no pin
at all, so the only way to make the chain true is to hold it.
`action-checkout-and-setup` becomes an explicit `actions/checkout` plus a local
`setup-environment` action -- Node, Corepack, Yarn's download cache, install.
Checkout cannot live inside it: `uses: ./...` resolves against the workspace,
so the repository has to be on disk before the runner can find the file.
The local action is narrower than the one it replaces. It drops the
node_modules cache and the lookup-only fast path, keeping only Yarn's download
cache, because those turn on a cache key that has to account for the native
rebuilds `postinstall` runs. `yarn install` runs every job now, which costs
time and buys the guarantee that what is on disk matches the lockfile.
`check-changelog` cloned github-tools to run a script over the diff. The local
job asks the same question directly: every package with a CHANGELOG that this
pull request touches must have that changelog in the diff too. It compares
against the merge base rather than the base tip, so commits landing on main
afterwards are not read as this branch's. `no-changelog` still opts out.
`check-skip-merge-queue` is ported as-is, its two scripts unchanged, onto a
pinned `actions/github-script`.
The release path still calls `action-npm-publish` and `action-is-release`,
which have the same defect. Both are gated on `push`, so they block releases
rather than pull requests, and neither is part of the `all-jobs-complete` gate.
Publishing is where a mistake is expensive and OIDC permissions were only just
fixed in #1041, so they are better handled upstream than rewritten here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5c2bd57. Configure here.
| # A package with no changelog is private and releases nothing, so | ||
| # there is no entry for it to be missing. This is the same set | ||
| # `yarn changelog:validate` covers, which runs `--no-private`. | ||
| [[ -f "$changelog" ]] || continue |
There was a problem hiding this comment.
Private packages fail changelog presence
Medium Severity
The presence check treats any packages/*/CHANGELOG.md as a published package. Several private workspaces ship a changelog from the template, including agentmask, create-package, kernel-agents, template-package, and sample-services. Edits there now fail this job even though yarn changelog:validate skips them with --no-private.
Reviewed by Cursor Bugbot for commit 5c2bd57. Configure here.


Stacked on #1043. That PR pins our own action references; this one closes the gap it exposed.
Why #1043 wasn't enough
The policy resolves actions transitively. Pinning the composite action we call does nothing if that action reaches a mutable tag one layer down — which all three of the ones on the PR path do:
uses:action-checkout-and-setupcache/restore@v6,checkout@v7,setup-node@v6,action-retry-command@v1,cache/save@v6github-tools/check-changelogcheckout@v6×2,setup-node@v6github-tools/check-skip-merge-queuegithub-script@v9×2All are on their latest releases, so no upgrade reaches them, and they live in an org we no longer belong to. Holding the steps ourselves is the only way to make the pin true rather than decorative.
What changed
action-checkout-and-setup→ explicitactions/checkout+ localsetup-environment. 11 call sites. Checkout can't live inside the local action —uses: ./...resolves against the workspace, so the repo must be on disk before the runner can find the file.The local action is deliberately narrower: it keeps Yarn's download cache but drops the node_modules cache and the lookup-only fast path. Those turn on a cache key that has to account for the native rebuilds
postinstallruns (better-sqlite3,node-datachannel), and a subtly wrong key yields a job that passes against stale binaries.yarn installnow runs every job — that costs time and buys the guarantee that what's on disk matches the lockfile.is-high-risk-environment: trueon the three publish jobs is preserved ascache: false.check-changelog→ a local job. The upstream action cloned github-tools and ran a script over the diff. The local one asks the same question directly: every package with aCHANGELOG.mdthat this PR touches must have that changelog in the diff too. Compares against the merge base, not the base tip, so commits landing on main afterwards aren't read as this branch's changes.no-changelogstill opts out. Packages without a changelog are private and skipped — the same setyarn changelog:validatecovers.Tested against real history before committing:
.githubonlybe4f406— code changed, changelog came later9b4717f— changelog-only commit19689c6— private package onlycheck-skip-merge-queue→ ported as-is, both scripts unchanged, onto a pinnedactions/github-script.What this does not fix
The release path still calls
MetaMask/action-npm-publishandMetaMask/action-is-release, which have the same defect. Both are gated ongithub.event_name == 'push'and neither is part of theall-jobs-completegate, so they block releases, not pull requests. Publishing is where a mistake is expensive and the OIDC permissions were only just fixed in #1041, so those are better pinned upstream than rewritten here.So: PR and merge-queue CI should go green with this; cutting a release still needs either upstream pins or a narrow policy exception for those two.
Verification
actionlint1.6.25 — the version and download pin theCheck workflowsjob uses — passes clean.uses:remains anywhere in.github/.prettier --checkclean.detect-changestreats.github/actions/*as code, so this PR runs the full lint/build/test matrix on both Node versions — the newsetup-environmentaction gets exercised by all seven job types here rather than after merge.🤖 Generated with Claude Code
Note
Medium Risk
CI and release-adjacent workflow behavior changes (stricter changelog diff, slower installs, merge-queue skip logic); no application runtime code, but misconfiguration could block merges or weaken publish-job cache isolation if
cache: falseis omitted.Overview
Replaces three MetaMask composite actions whose transitive
uses:still pointed at mutable tags, so PR-path CI can pin the full dependency chain locally.Environment setup: Adds
setup-environment(pinnedsetup-node,cache, Corepack,yarn install --immutable) and swaps everyaction-checkout-and-setupcall for explicitactions/checkoutplus that action. Publish jobs passcache: falseinstead ofis-high-risk-environment. The local action dropsnode_modulescaching and always runs a full install so nativepostinstallbinaries stay aligned with the lockfile; only Yarn’s download cache remains.Changelog PR check: Inlines the former
github-tools/check-changelogas a bash step: diff from merge base to HEAD, require each touchedpackages/*with aCHANGELOG.mdto include that file in the diff, honorno-changelog, skip packages without a changelog.Merge queue: Vendors
check-skip-merge-queueunder.github/actionswith pinnedactions/github-script;main.ymlcalls the local copy. Behavior unchanged—outputs whether the PR is first in queue and up to date with base for skip gating.Reviewed by Cursor Bugbot for commit 5c2bd57. Bugbot is set up for automated code reviews on this repo. Configure here.