From 4023ec2fd27c16cb564cf5ccdd16452edcbae76a Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:15:00 -0400 Subject: [PATCH] perf(ci): install the remaining exact Node pins without GitHub API calls Switch the 16 remaining actions/setup-node steps that read .node-version to the local setup-pinned-node action, so they stop spending two job-token manifest reads per step on a tool-cache miss. - pr-require-checks: the six lanes whose root checkout is the code under test use ./.github/actions/setup-pinned-node; ci-status uses main's copy from its sparse .base checkout. - pr-review-hosted, pr-review-security-hosted, pr-review-main and eval-browser-dispatch use ./.base/... (main's copy). - maintenance-check-upstream-drift uses the root copy (schedule or dispatch). - maintenance-audit-test-selection stays on setup-node (cache: npm, about 0 calls/h). - ci-lanes-pr-hold.test.sh accepts a ci-status with no setup-node step. Co-Authored-By: Claude Opus 5.5 --- .github/actions/setup-pinned-node/README.md | 14 +++++++++++++- .github/workflows/eval-browser-dispatch.yml | 3 +-- .../maintenance-check-upstream-drift.yml | 2 +- .github/workflows/pr-require-checks.yml | 16 ++++++++-------- .github/workflows/pr-review-hosted.yml | 8 ++++---- .github/workflows/pr-review-main.yml | 9 +++------ .github/workflows/pr-review-security-hosted.yml | 8 ++++---- scripts/ci-lanes-pr-hold.test.sh | 3 +-- 8 files changed, 35 insertions(+), 28 deletions(-) diff --git a/.github/actions/setup-pinned-node/README.md b/.github/actions/setup-pinned-node/README.md index dad40a7e93..aa8e3674ad 100644 --- a/.github/actions/setup-pinned-node/README.md +++ b/.github/actions/setup-pinned-node/README.md @@ -32,6 +32,18 @@ mismatch, or a `SHASUMS256.txt` without the tarball's line. node-version-file: .base/.node-version ``` -A job with a sparse `.base` checkout lists `/.github/actions/setup-pinned-node/` in it. A composite +A job with a sparse `.base` checkout lists `/.github/actions/setup-pinned-node/` in it. A job whose +root checkout is the code under test, and which already runs that code, uses the root copy, so a +pull request that changes this action is tested by its own CI: + +```yaml +- name: Set up Node + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] + with: + node-version-file: .node-version +``` + +Never use the root form in a job that runs with main's trust and has a pull request checked out at +the root: there it would run pull request code. A composite action in the same checkout runs `install-node.sh` from its sibling directory instead, as `check-wip-cap` does. Tests: `scripts/setup-pinned-node.test.sh`. diff --git a/.github/workflows/eval-browser-dispatch.yml b/.github/workflows/eval-browser-dispatch.yml index 2c4e311ea5..bc4485ba36 100644 --- a/.github/workflows/eval-browser-dispatch.yml +++ b/.github/workflows/eval-browser-dispatch.yml @@ -140,10 +140,9 @@ jobs: persist-credentials: false - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Check the kill switch id: kill diff --git a/.github/workflows/maintenance-check-upstream-drift.yml b/.github/workflows/maintenance-check-upstream-drift.yml index 675b398312..66891dec76 100644 --- a/.github/workflows/maintenance-check-upstream-drift.yml +++ b/.github/workflows/maintenance-check-upstream-drift.yml @@ -62,7 +62,7 @@ jobs: with: persist-credentials: false - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .node-version - name: Run the drift check diff --git a/.github/workflows/pr-require-checks.yml b/.github/workflows/pr-require-checks.yml index cd24be2eac..14c3d3a8d2 100644 --- a/.github/workflows/pr-require-checks.yml +++ b/.github/workflows/pr-require-checks.yml @@ -1221,7 +1221,7 @@ jobs: # not enforce the policy does not pay for its toolchain either. - name: Set up Node if: needs.select-tests.outputs.run_workflows == 'true' - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .node-version - name: Restore the npm cache @@ -2055,7 +2055,7 @@ jobs: # pay for it. - name: Set up Node if: needs.select-tests.outputs.run_tests == 'true' - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .node-version @@ -2199,7 +2199,7 @@ jobs: # claude CLI from node_modules/.bin, and node for the prerequisite gate. - name: Set up Node if: needs.select-tests.outputs.run_tests == 'true' - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .node-version - name: Restore the npm cache @@ -2701,7 +2701,7 @@ jobs: # hosted and local runs exercise the same contract suites instead of # inheriting different image tool inventories. - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .node-version - name: Restore the npm cache @@ -2969,7 +2969,7 @@ jobs: with: filter: blob:none - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .node-version - name: Restore the npm cache @@ -3096,7 +3096,7 @@ jobs: # from, so a change to any one of them invalidates the cache the others # share. - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .node-version - name: Restore the npm cache @@ -4122,6 +4122,7 @@ jobs: persist-credentials: false sparse-checkout: | /.node-version + /.github/actions/setup-pinned-node/ /plugins/source-control/skills/babysit-prs/scripts/*.py /plugins/source-control/lib/parse-concern-value.sh /plugins/source-control/lib/yaml-subset.awk @@ -4135,10 +4136,9 @@ jobs: sparse-checkout-cone-mode: false - name: Set up Node if: ${{ !cancelled() && (github.event_name == 'pull_request' || github.event_name == 'merge_group') }} - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false # THE HOLD STEPS READ THEIR CONFIG FROM THE BASE COMMIT, ONCE. The tracker # config names the human-gated label, the trusted-actor list names who # may lift a hold and, with pr-pipeline.yaml's merge.agent-identity, the diff --git a/.github/workflows/pr-review-hosted.yml b/.github/workflows/pr-review-hosted.yml index ca664a1c88..3b78b256d4 100644 --- a/.github/workflows/pr-review-hosted.yml +++ b/.github/workflows/pr-review-hosted.yml @@ -80,6 +80,7 @@ jobs: /.github/actions/check-trusted-trigger/ /.github/actions/log-api-budget/ /.github/actions/resolve-config/ + /.github/actions/setup-pinned-node/ /.github/CODEOWNERS /.github/standards/github-actions-conventions/vocabulary.json /.node-version @@ -88,10 +89,9 @@ jobs: sparse-checkout-cone-mode: false - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Resolve the review model id: models @@ -132,14 +132,14 @@ jobs: persist-credentials: false sparse-checkout: | /.github/actions/check-kill-switch/ + /.github/actions/setup-pinned-node/ /.node-version sparse-checkout-cone-mode: false - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Check the kill switch id: kill diff --git a/.github/workflows/pr-review-main.yml b/.github/workflows/pr-review-main.yml index 4c9e9a509c..a31be0b653 100644 --- a/.github/workflows/pr-review-main.yml +++ b/.github/workflows/pr-review-main.yml @@ -117,10 +117,9 @@ jobs: run: echo "$LANE-base-sha=$(git -C .base rev-parse --verify HEAD)" >>"$GITHUB_OUTPUT" - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Check the kill switch id: kill @@ -595,10 +594,9 @@ jobs: run: echo "$LANE-base-sha=$(git -C .base rev-parse --verify HEAD)" >>"$GITHUB_OUTPUT" - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Check the kill switch id: kill @@ -1065,10 +1063,9 @@ jobs: run: echo "$LANE-base-sha=$(git -C .base rev-parse --verify HEAD)" >>"$GITHUB_OUTPUT" - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Check the kill switch id: kill diff --git a/.github/workflows/pr-review-security-hosted.yml b/.github/workflows/pr-review-security-hosted.yml index 2ac4cd2584..45f7a8ff7b 100644 --- a/.github/workflows/pr-review-security-hosted.yml +++ b/.github/workflows/pr-review-security-hosted.yml @@ -85,6 +85,7 @@ jobs: /.github/actions/check-trusted-trigger/ /.github/actions/log-api-budget/ /.github/actions/resolve-config/ + /.github/actions/setup-pinned-node/ /.github/CODEOWNERS /.github/standards/github-actions-conventions/vocabulary.json /.node-version @@ -93,10 +94,9 @@ jobs: sparse-checkout-cone-mode: false - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Resolve the review model id: models @@ -137,14 +137,14 @@ jobs: persist-credentials: false sparse-checkout: | /.github/actions/check-kill-switch/ + /.github/actions/setup-pinned-node/ /.node-version sparse-checkout-cone-mode: false - name: Set up Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] with: node-version-file: .base/.node-version - package-manager-cache: false - name: Check the kill switch id: kill diff --git a/scripts/ci-lanes-pr-hold.test.sh b/scripts/ci-lanes-pr-hold.test.sh index cefbe78a09..2028e9c205 100755 --- a/scripts/ci-lanes-pr-hold.test.sh +++ b/scripts/ci-lanes-pr-hold.test.sh @@ -410,8 +410,7 @@ fi ci_status="$(awk '/^ ci-status:/ { on = 1; print; next } on && /^ [A-Za-z0-9_-]+:$/ { exit } on { print }' "$WORKFLOW")" if grep -qE '^ +uses: actions/cache(/restore)?@' <<<"$ci_status"; then fail "ci-status restores an Actions cache" -elif [[ "$(grep -A3 -E '^ +uses: actions/setup-node@' <<<"$ci_status")" == *'package-manager-cache: false'* ]] && - [[ "$(grep -cE '^ +uses: actions/setup-node@' <<<"$ci_status")" -eq "$(grep -cE '^ +package-manager-cache: false$' <<<"$ci_status")" ]]; then +elif [[ "$(grep -cE '^ +uses: actions/setup-node@' <<<"$ci_status")" -eq "$(grep -cE '^ +package-manager-cache: false$' <<<"$ci_status")" ]]; then ok "ci-status restores no Actions cache, setup-node's package cache included" else fail "ci-status's setup-node may restore a package cache (no package-manager-cache: false)"