From 01d2d6358903dea6fee2158031900d575d0ec234 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:33:45 -0700 Subject: [PATCH 01/22] chore(ci): bump jactionlint to 2.3.0 Generated with AI Co-Authored-By: Claude --- mise.lock | 70 +++++++++++++++++++++++++++---------------------------- mise.toml | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/mise.lock b/mise.lock index 83da59b571..4b64eed8ee 100644 --- a/mise.lock +++ b/mise.lock @@ -172,74 +172,74 @@ url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/4 provenance = "github-attestations" [[tools.jactionlint]] -version = "1.8.2" +version = "2.3.0" backend = "github:jdx/jactionlint" -specifiers = ["1"] +specifiers = ["2"] [tools.jactionlint."platforms.linux-arm64"] -checksum = "sha256:7d618749dbddd762a14bd491ea97bc4f6599626f830a11edc6c6522ead8bcd05" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338576" +checksum = "sha256:deea7744e5769e1e5cc8c9898a838f80a941ec68476d9f2814a61474d5652dac" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210370" provenance = "github-attestations" [tools.jactionlint."platforms.linux-arm64-musl"] -checksum = "sha256:7d618749dbddd762a14bd491ea97bc4f6599626f830a11edc6c6522ead8bcd05" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338576" +checksum = "sha256:deea7744e5769e1e5cc8c9898a838f80a941ec68476d9f2814a61474d5652dac" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210370" provenance = "github-attestations" [tools.jactionlint."platforms.linux-x64"] -checksum = "sha256:6533bd8047fc012a3f66238d84f65cacdac669da00b9f6cf042c1734c252bc93" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338580" +checksum = "sha256:907e6f6a59e181879f7e6936e19ee2eca1cbc51de134fb85460cfa8d65a71f28" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210498" provenance = "github-attestations" [tools.jactionlint."platforms.linux-x64-baseline"] -checksum = "sha256:6533bd8047fc012a3f66238d84f65cacdac669da00b9f6cf042c1734c252bc93" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338580" +checksum = "sha256:907e6f6a59e181879f7e6936e19ee2eca1cbc51de134fb85460cfa8d65a71f28" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210498" provenance = "github-attestations" [tools.jactionlint."platforms.linux-x64-musl"] -checksum = "sha256:6533bd8047fc012a3f66238d84f65cacdac669da00b9f6cf042c1734c252bc93" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338580" +checksum = "sha256:907e6f6a59e181879f7e6936e19ee2eca1cbc51de134fb85460cfa8d65a71f28" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210498" provenance = "github-attestations" [tools.jactionlint."platforms.linux-x64-musl-baseline"] -checksum = "sha256:6533bd8047fc012a3f66238d84f65cacdac669da00b9f6cf042c1734c252bc93" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338580" +checksum = "sha256:907e6f6a59e181879f7e6936e19ee2eca1cbc51de134fb85460cfa8d65a71f28" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210498" provenance = "github-attestations" [tools.jactionlint."platforms.macos-arm64"] -checksum = "sha256:de9eb05af3cde409dc0b37fe2fd6a21b875a575374e7b26c02d45a7086645c28" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338605" +checksum = "sha256:f96afa36578b21290203ae8df4fa7ac3bf5aba08ed288df28cb0530af7ca6e55" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210452" provenance = "github-attestations" [tools.jactionlint."platforms.macos-x64"] -checksum = "sha256:42790b2565759b7f4a81696fb9034b2060f97f551d2ced54cf722c9de9abe6b2" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338604" +checksum = "sha256:a61006b669ec0ab1266c73e3561eecf839537eb6e8b3b6be766a55ba17fcbe42" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210428" provenance = "github-attestations" [tools.jactionlint."platforms.macos-x64-baseline"] -checksum = "sha256:42790b2565759b7f4a81696fb9034b2060f97f551d2ced54cf722c9de9abe6b2" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338604" +checksum = "sha256:a61006b669ec0ab1266c73e3561eecf839537eb6e8b3b6be766a55ba17fcbe42" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210428" provenance = "github-attestations" [tools.jactionlint."platforms.windows-x64"] -checksum = "sha256:fd7be539cd3276d5178f85ef140c1bb52395b1ec1f59b1585050e1be3b0b194b" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_windows_amd64.zip" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338616" +checksum = "sha256:9864d1045bd37fddb2df33bf4d6b45de205c0ce133c52a225928b7ede7d7f3eb" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_windows_amd64.zip" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210534" provenance = "github-attestations" [tools.jactionlint."platforms.windows-x64-baseline"] -checksum = "sha256:fd7be539cd3276d5178f85ef140c1bb52395b1ec1f59b1585050e1be3b0b194b" -url = "https://github.com/jdx/jactionlint/releases/download/v1.8.2/jactionlint_1.8.2_windows_amd64.zip" -url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/611338616" +checksum = "sha256:9864d1045bd37fddb2df33bf4d6b45de205c0ce133c52a225928b7ede7d7f3eb" +url = "https://github.com/jdx/jactionlint/releases/download/v2.3.0/jactionlint_2.3.0_windows_amd64.zip" +url_api = "https://api.github.com/repos/jdx/jactionlint/releases/assets/662210534" provenance = "github-attestations" [[tools.node]] diff --git a/mise.toml b/mise.toml index 5596a9bb89..a197d4db4c 100644 --- a/mise.toml +++ b/mise.toml @@ -3,7 +3,7 @@ min_version = '2026.10.17' [tools] go = "1.26.5" golangci-lint = "2.12.2" -jactionlint = "1" +jactionlint = "2" shellcheck = "0.11.0" [settings] From eb74f6c158f57c779823730d723d3c0878ea41f0 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:34:18 -0700 Subject: [PATCH 02/22] chore(ci): migrate the jactionlint config to the 2.x rules key Generated with AI Co-Authored-By: Claude --- .github/jactionlint.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/jactionlint.yaml b/.github/jactionlint.yaml index 11d6ccd219..c8d054dfb2 100644 --- a/.github/jactionlint.yaml +++ b/.github/jactionlint.yaml @@ -1,4 +1,5 @@ -require-commit-hash: true # actions, reusable workflows and docker:// must be pinned to a full SHA / @sha256 digest +rules: + unpinned-uses: error # actions, reusable workflows and docker:// must be pinned to a full SHA / @sha256 digest self-hosted-runner: labels: - arm-runner From b7eff2567e7ae806476ec177687180cf2fceb35f Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:34:20 -0700 Subject: [PATCH 03/22] chore(ci): keep jactionlint out of the vendored submodules Generated with AI Co-Authored-By: Claude --- .github/jactionlint.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/jactionlint.yaml b/.github/jactionlint.yaml index c8d054dfb2..95a58d46e0 100644 --- a/.github/jactionlint.yaml +++ b/.github/jactionlint.yaml @@ -9,3 +9,8 @@ self-hosted-runner: - blacksmith-8vcpu-ubuntu-2404 - blacksmith-8vcpu-windows-2025 - blacksmith-32vcpu-ubuntu-2404 +paths: + # Git submodules of other projects; CI never checks them out. + ".repos/**": + ignore: + - ".*" From 84f7717fa345c5187924f256f721804d6eb6eada Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:34:21 -0700 Subject: [PATCH 04/22] chore(ci): turn off the jactionlint missing-timeout rule Generated with AI Co-Authored-By: Claude --- .github/jactionlint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/jactionlint.yaml b/.github/jactionlint.yaml index 95a58d46e0..74d68a8c68 100644 --- a/.github/jactionlint.yaml +++ b/.github/jactionlint.yaml @@ -1,4 +1,5 @@ rules: + missing-timeout: off unpinned-uses: error # actions, reusable workflows and docker:// must be pinned to a full SHA / @sha256 digest self-hosted-runner: labels: From e8cb0c66d96e05e5fc2b68c53f0bc043a5c83041 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:34:22 -0700 Subject: [PATCH 05/22] chore(ci): turn off the jactionlint concurrency-limits rule Generated with AI Co-Authored-By: Claude --- .github/jactionlint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/jactionlint.yaml b/.github/jactionlint.yaml index 74d68a8c68..6bda2dc1e2 100644 --- a/.github/jactionlint.yaml +++ b/.github/jactionlint.yaml @@ -1,5 +1,6 @@ rules: missing-timeout: off + concurrency-limits: off unpinned-uses: error # actions, reusable workflows and docker:// must be pinned to a full SHA / @sha256 digest self-hosted-runner: labels: From 958d297c8dc9e397395c28f78af0112da98bcea2 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:36:13 -0700 Subject: [PATCH 06/22] ci: grant write scopes per job instead of per workflow Single-job workflows move their write scopes under the job, so a job added later starts with no token access. contribution-gate grants both jobs the scopes the script needs to comment on and close pull requests. release-smoke-test's caller job keeps the four scopes release-shared's job tree declares: contents and id-token for publish, pull-requests for the notes proposal, actions for the live-e2e gate. Generated with AI Co-Authored-By: Claude --- .github/workflows/cli-go-mirror-image.yml | 9 +++++---- .github/workflows/cli-go-tag-pkg.yml | 5 +++-- .github/workflows/close-stale-issues-and-prs.yml | 9 +++++---- .github/workflows/contribution-gate.yml | 13 +++++++++---- .github/workflows/release-smoke-test.yml | 15 ++++++++------- .github/workflows/reopen-stale-issue.yml | 2 ++ 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cli-go-mirror-image.yml b/.github/workflows/cli-go-mirror-image.yml index 889ecaf9ae..284118c964 100644 --- a/.github/workflows/cli-go-mirror-image.yml +++ b/.github/workflows/cli-go-mirror-image.yml @@ -19,14 +19,15 @@ on: required: true type: string -permissions: - contents: read - packages: write - id-token: write +permissions: {} jobs: mirror: runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write steps: - id: strip env: diff --git a/.github/workflows/cli-go-tag-pkg.yml b/.github/workflows/cli-go-tag-pkg.yml index 9185ad5771..3563c4077b 100644 --- a/.github/workflows/cli-go-tag-pkg.yml +++ b/.github/workflows/cli-go-tag-pkg.yml @@ -8,13 +8,14 @@ on: required: true type: string -permissions: - contents: write +permissions: {} jobs: tag: name: Create pkg tag runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/close-stale-issues-and-prs.yml b/.github/workflows/close-stale-issues-and-prs.yml index e6ecb519a3..fb43466983 100644 --- a/.github/workflows/close-stale-issues-and-prs.yml +++ b/.github/workflows/close-stale-issues-and-prs.yml @@ -22,10 +22,7 @@ on: type: string default: "security,pinned,do-not-close,keep-open,do not merge" -permissions: - contents: read - issues: write - pull-requests: write +permissions: {} concurrency: group: close-stale-issues-and-prs @@ -35,6 +32,10 @@ jobs: close-stale: runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: read + issues: write + pull-requests: write steps: - name: Close stale issues and PRs uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/contribution-gate.yml b/.github/workflows/contribution-gate.yml index af7cab6ac5..ee040ae8c3 100644 --- a/.github/workflows/contribution-gate.yml +++ b/.github/workflows/contribution-gate.yml @@ -16,10 +16,7 @@ on: type: boolean default: false -permissions: - pull-requests: write - issues: read - contents: read +permissions: {} concurrency: # Per-PR for pull_request_target; unique per run for manual sweeps (which @@ -31,6 +28,10 @@ jobs: gate-single: name: Contribution Gate runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: read + contents: read # Exempt maintainers/collaborators and bots. External contributors are gated. if: > github.event_name == 'pull_request_target' && @@ -58,6 +59,10 @@ jobs: gate-all: name: Contribution Gate (all open PRs) runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: read + contents: read if: github.event_name == 'workflow_dispatch' steps: # Base-branch checkout only — the sweep makes API calls and never runs diff --git a/.github/workflows/release-smoke-test.yml b/.github/workflows/release-smoke-test.yml index 27ca8a2ebc..0aa4ff6b17 100644 --- a/.github/workflows/release-smoke-test.yml +++ b/.github/workflows/release-smoke-test.yml @@ -26,17 +26,18 @@ concurrency: group: release-smoke-test-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: true -permissions: - # release-shared.yml declares privileged publish jobs. They are gated by - # dry_run here, but GitHub validates nested-workflow permissions at startup. - actions: read - contents: write - id-token: write - pull-requests: write +permissions: {} jobs: smoke: name: Run release smoke tests + # release-shared.yml declares privileged publish jobs. They are gated by + # dry_run here, but GitHub validates nested-workflow permissions at startup. + permissions: + actions: read + contents: write + id-token: write + pull-requests: write # Fork heads are skipped: the shared workflow declares write permissions a # fork pull request token cannot hold, and hotfixes come from this repository. if: | diff --git a/.github/workflows/reopen-stale-issue.yml b/.github/workflows/reopen-stale-issue.yml index dd7572b617..fae44bd50c 100644 --- a/.github/workflows/reopen-stale-issue.yml +++ b/.github/workflows/reopen-stale-issue.yml @@ -7,6 +7,8 @@ on: permissions: contents: read + # The script only reopens, unlabels and comments; there is no narrower scope. + # jactionlint ignore=excessive-permissions issues: write concurrency: From c408fbdba3bf79f9f79c3b1bb10a3adc12dfebf2 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:36:43 -0700 Subject: [PATCH 07/22] ci: request only the scopes each release-notes App token uses The authorize job reads a team membership and comments on the pull request. The apply job edits the release, comments, and closes the pull request with its branch; workflows write covers a release whose tagged commit changed a workflow file, as release-shared already requests. The propose job pushes a branch, creates a label, and opens the pull request. Generated with AI Co-Authored-By: Claude --- .github/workflows/apply-release-notes.yml | 11 +++++++++-- .github/workflows/propose-release-notes.yml | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/apply-release-notes.yml b/.github/workflows/apply-release-notes.yml index 753028e5cc..f23d5609bc 100644 --- a/.github/workflows/apply-release-notes.yml +++ b/.github/workflows/apply-release-notes.yml @@ -27,13 +27,14 @@ jobs: outputs: authorized: ${{ steps.check.outputs.authorized }} steps: - # App token needs org membership read, repo write to edit the release, and PR write to - # comment and close. + # App token needs org membership read, and PR write to comment. - id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.GH_APP_CLIENT_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + permission-members: read + permission-pull-requests: write - name: Authorize approver against supabase/cli team id: check @@ -66,11 +67,17 @@ jobs: contents: write pull-requests: write steps: + # App token needs repo write to edit the release (plus workflows write when the tagged + # commit touched .github/workflows) and to delete the branch, and PR write to comment and + # close. - id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.GH_APP_CLIENT_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + permission-contents: write + permission-workflows: write + permission-pull-requests: write # Checkout the PR head so reviewer edits made before approval are captured; # apply-release-notes.ts reads from the working tree. diff --git a/.github/workflows/propose-release-notes.yml b/.github/workflows/propose-release-notes.yml index 04f9359071..3fbf0d9136 100644 --- a/.github/workflows/propose-release-notes.yml +++ b/.github/workflows/propose-release-notes.yml @@ -61,6 +61,9 @@ jobs: with: client-id: ${{ vars.GH_APP_CLIENT_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + permission-contents: write + permission-issues: write + permission-pull-requests: write - uses: useblacksmith/checkout@6fd481652155169ed4d2f25ebaf97464f685175f # v1 with: From c99230405597356f16268f0b802917e617099717 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:38:14 -0700 Subject: [PATCH 08/22] ci: create GitHub releases with gh instead of action-gh-release Both steps skip when the release already exists, which action-gh-release handled by updating it, and verify the tag the earlier step pushed. The config release stays off "latest" and the CLI release stays a draft for the asset upload steps that follow. Generated with AI Co-Authored-By: Claude --- .github/workflows/release-config.yml | 29 ++++++++++++++++------------ .github/workflows/release-shared.yml | 22 +++++++++++++++------ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release-config.yml b/.github/workflows/release-config.yml index 2b79a91614..08eb4b6a98 100644 --- a/.github/workflows/release-config.yml +++ b/.github/workflows/release-config.yml @@ -355,19 +355,24 @@ jobs: fi echo "dist-tag ${NPM_TAG} -> ${VERSION} confirmed." + # The CLI's install scripts and setup-cli resolve releases/latest/download/..., so a + # config release must never become the repo's "latest" release. Idempotent: skips if + # the release already exists. - name: Create GitHub Release - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 - with: - token: ${{ github.token }} - tag_name: config-v${{ needs.plan.outputs.version }} - name: "@supabase/config v${{ needs.plan.outputs.version }}" - body_path: ${{ runner.temp }}/config-release/config-release-notes.md - draft: false - prerelease: false - # The CLI's install scripts and setup-cli resolve - # releases/latest/download/..., so a config release must never - # become the repo's "latest" release. - make_latest: "false" + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + tag="config-v${VERSION}" + if gh release view "${tag}" >/dev/null 2>&1; then + echo "Release ${tag} already exists; skipping." + else + gh release create "${tag}" \ + --verify-tag \ + --title "@supabase/config v${VERSION}" \ + --notes-file "${RUNNER_TEMP}/config-release/config-release-notes.md" \ + --latest=false + fi # Pages Slack the moment a real run arms the config-release approval gate. Runs in parallel # with the publish job's `waiting` state (needs only `plan`), so the ping and the pending diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index 1f0e0a13c9..2f872d6fef 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -377,13 +377,23 @@ jobs: done # Created empty; the next step uploads the assets so each one can be retried on its own. + # Idempotent: skips if the release already exists. - name: Create draft GitHub Release - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 - with: - tag_name: v${{ inputs.version }} - name: v${{ inputs.version }} - draft: true - prerelease: ${{ inputs.prerelease }} + env: + GH_TOKEN: ${{ github.token }} + PRERELEASE: ${{ inputs.prerelease }} + run: | + set -euo pipefail + tag="v${VERSION}" + if gh release view "${tag}" >/dev/null 2>&1; then + echo "Release ${tag} already exists; skipping." + else + args=(--draft --verify-tag --title "${tag}") + if [[ "$PRERELEASE" == true ]]; then + args+=(--prerelease) + fi + gh release create "${tag}" "${args[@]}" + fi # One asset at a time with per-asset retries; see apps/cli/scripts/upload-release-assets.ts. - name: Upload release assets From 8f19bf3b22651bb69c03f734fc47f3975c1f1d2b Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:38:56 -0700 Subject: [PATCH 09/22] ci: stop contribution-gate checkouts persisting the token The gate script talks to the API with its own token and never runs git. Generated with AI Co-Authored-By: Claude --- .github/workflows/contribution-gate.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/contribution-gate.yml b/.github/workflows/contribution-gate.yml index ee040ae8c3..e052137ad3 100644 --- a/.github/workflows/contribution-gate.yml +++ b/.github/workflows/contribution-gate.yml @@ -43,6 +43,8 @@ jobs: # Checks out the base repo (default for pull_request_target), so the gate # runs trusted code from the base branch, never the untrusted PR head. - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version-file: ".bun-version" @@ -68,6 +70,8 @@ jobs: # Base-branch checkout only — the sweep makes API calls and never runs # any PR's code. - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version-file: ".bun-version" From 5eb9c91e554486256779e8fc31e7a55589fe33d4 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:38:57 -0700 Subject: [PATCH 10/22] ci: drop job outputs no other job reads Generated with AI Co-Authored-By: Claude --- .github/workflows/ai-review.yml | 1 - .github/workflows/cli-go-pg-prove.yml | 2 -- .github/workflows/cli-go-publish-migra.yml | 2 -- 3 files changed, 5 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 582874175f..74d7508729 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -65,7 +65,6 @@ jobs: outputs: should_run: ${{ steps.resolve.outputs.should_run }} pr_number: ${{ steps.resolve.outputs.pr_number }} - head_ref: ${{ steps.resolve.outputs.head_ref }} # Every job reviews this commit, so a push during the run is never reported as reviewed. head_sha: ${{ steps.resolve.outputs.head_sha }} trigger: ${{ steps.resolve.outputs.trigger }} diff --git a/.github/workflows/cli-go-pg-prove.yml b/.github/workflows/cli-go-pg-prove.yml index 6a4414ee3f..3c836bbdce 100644 --- a/.github/workflows/cli-go-pg-prove.yml +++ b/.github/workflows/cli-go-pg-prove.yml @@ -38,8 +38,6 @@ jobs: arch: arm64 runs-on: ${{ matrix.runner }} timeout-minutes: 180 - outputs: - image_digest: ${{ steps.build.outputs.digest }} steps: - run: docker context create builders - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 diff --git a/.github/workflows/cli-go-publish-migra.yml b/.github/workflows/cli-go-publish-migra.yml index c3b0a56333..7e37210911 100644 --- a/.github/workflows/cli-go-publish-migra.yml +++ b/.github/workflows/cli-go-publish-migra.yml @@ -38,8 +38,6 @@ jobs: arch: arm64 runs-on: ${{ matrix.runner }} timeout-minutes: 180 - outputs: - image_digest: ${{ steps.build.outputs.digest }} steps: - run: docker context create builders - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 From 3e249aaec2d2f501e8ac510a0e612f125f9e744e Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:39:33 -0700 Subject: [PATCH 11/22] ci: fail pipelines on the first broken command The default run shell is bash -e without pipefail, so a failing go, jq, pnpm or docker command at the head of a pipeline was hidden by the exit status of the command after it. Generated with AI Co-Authored-By: Claude --- .github/workflows/cli-go-ci.yml | 1 + .github/workflows/dispatch-cli-e2e-ci.yml | 1 + .github/workflows/fill-sticky-disks.yml | 2 ++ .github/workflows/sync-repair.yml | 1 + 4 files changed, 5 insertions(+) diff --git a/.github/workflows/cli-go-ci.yml b/.github/workflows/cli-go-ci.yml index 2ae77eac79..97ee9dc277 100644 --- a/.github/workflows/cli-go-ci.yml +++ b/.github/workflows/cli-go-ci.yml @@ -31,6 +31,7 @@ jobs: # Required by: internal/utils/credentials/keyring_test.go - uses: t1m0thyj/unlock-keyring@cbcf205c879ebd86add70bab3a6abfcce59a5cae # v1.2.0 - run: | + set -o pipefail pkgs=$(go list ./pkg/... | grep -Ev 'pkg/api' | paste -sd ',' -) go tool gotestsum -- -race -v -count=1 ./... \ -coverpkg="./cmd/...,./internal/...,${pkgs}" -coverprofile=coverage.out diff --git a/.github/workflows/dispatch-cli-e2e-ci.yml b/.github/workflows/dispatch-cli-e2e-ci.yml index ed5aa0f22a..0fc4f0a9f9 100644 --- a/.github/workflows/dispatch-cli-e2e-ci.yml +++ b/.github/workflows/dispatch-cli-e2e-ci.yml @@ -37,6 +37,7 @@ jobs: CLI_SHA: ${{ github.event.pull_request.head.sha }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | + set -o pipefail echo "Dispatching cli-e2e-ci live run for PR #${PR_NUMBER} @ ${CLI_SHA}" # Build the nested client_payload with jq — `gh api -f` sends a flat # body and would not nest `client_payload.*` correctly. diff --git a/.github/workflows/fill-sticky-disks.yml b/.github/workflows/fill-sticky-disks.yml index 2440f388d1..0e9da129b8 100644 --- a/.github/workflows/fill-sticky-disks.yml +++ b/.github/workflows/fill-sticky-disks.yml @@ -72,6 +72,7 @@ jobs: - name: Pull stack package images run: | + set -o pipefail pnpm --silent --filter @supabase/stack prepare:test-artifacts --images | xargs --max-procs 4 --max-args 1 docker pull --quiet @@ -79,6 +80,7 @@ jobs: - name: Prune Docker before saving its image disk if: always() run: | + set -o pipefail docker ps --all --quiet | xargs --no-run-if-empty docker rm --force docker system prune --force docker volume prune --all --force diff --git a/.github/workflows/sync-repair.yml b/.github/workflows/sync-repair.yml index a9002a4085..4f0510caa0 100644 --- a/.github/workflows/sync-repair.yml +++ b/.github/workflows/sync-repair.yml @@ -119,6 +119,7 @@ jobs: HEAD_SHA: ${{ steps.collect.outputs.head }} CHANGED: ${{ steps.repair.outputs.changed }} run: | + set -o pipefail if [ "$(gh pr view "$PR" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid)" != "$HEAD_SHA" ]; then echo "::notice::The sync branch moved since the checks settled; skipping." exit 0 From 7e9a683a97e937f5c286e3482d36a6cfa2f3a9f2 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:40:00 -0700 Subject: [PATCH 12/22] ci: pass the Claude model through a step output The sync workflows run on workflow_run with secrets, where a value written to GITHUB_ENV reaches every later step. A step output reaches only the three steps that start Claude. Generated with AI Co-Authored-By: Claude --- .github/workflows/sync-branches.yml | 8 ++++++-- .github/workflows/sync-repair.yml | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync-branches.yml b/.github/workflows/sync-branches.yml index 5eefcfbae3..464af54837 100644 --- a/.github/workflows/sync-branches.yml +++ b/.github/workflows/sync-branches.yml @@ -120,7 +120,8 @@ jobs: persist-credentials: false - name: Load Claude model - run: echo "CLAUDE_MODEL=$(jq -r .claude.opus trusted/.github/ai-models.json)" >> "$GITHUB_ENV" + id: model + run: echo "model=$(jq -r .claude.opus trusted/.github/ai-models.json)" >> "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -146,6 +147,7 @@ jobs: working-directory: trusted env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_MODEL: ${{ steps.model.outputs.model }} WORK_DIR: ${{ github.workspace }}/work run: | git -C "$WORK_DIR" config user.name "github-actions[bot]" @@ -246,7 +248,8 @@ jobs: persist-credentials: false - name: Load Claude model - run: echo "CLAUDE_MODEL=$(jq -r .claude.opus trusted/.github/ai-models.json)" >> "$GITHUB_ENV" + id: model + run: echo "model=$(jq -r .claude.opus trusted/.github/ai-models.json)" >> "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -276,6 +279,7 @@ jobs: working-directory: trusted env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_MODEL: ${{ steps.model.outputs.model }} WORK_DIR: ${{ github.workspace }}/work run: | git -C "$WORK_DIR" fetch --no-tags "$OUTPUT_DIR/sync.bundle" HEAD diff --git a/.github/workflows/sync-repair.yml b/.github/workflows/sync-repair.yml index 4f0510caa0..1b0c281ad1 100644 --- a/.github/workflows/sync-repair.yml +++ b/.github/workflows/sync-repair.yml @@ -55,7 +55,8 @@ jobs: persist-credentials: false - name: Load Claude model - run: echo "CLAUDE_MODEL=$(jq -r .claude.opus trusted/.github/ai-models.json)" >> "$GITHUB_ENV" + id: model + run: echo "model=$(jq -r .claude.opus trusted/.github/ai-models.json)" >> "$GITHUB_OUTPUT" - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: @@ -91,6 +92,7 @@ jobs: working-directory: trusted env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_MODEL: ${{ steps.model.outputs.model }} WORK_DIR: ${{ github.workspace }}/work HEAD_SHA: ${{ steps.collect.outputs.head }} run: | From 1e8378eac95f8667105a372aafa9ab6b57995069 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:41:08 -0700 Subject: [PATCH 13/22] ci: accept the sender check in bot-pr-auto-merge Generated with AI Co-Authored-By: Claude --- .github/workflows/bot-pr-auto-merge.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/bot-pr-auto-merge.yml b/.github/workflows/bot-pr-auto-merge.yml index 6745993567..3c2b5d7fbd 100644 --- a/.github/workflows/bot-pr-auto-merge.yml +++ b/.github/workflows/bot-pr-auto-merge.yml @@ -14,6 +14,9 @@ jobs: # Other apps (coding agents, CI vendors) also open PRs here and still need a maintainer. # Release-notes PRs are published by a maintainer approval and must never merge. Branch sync # PRs land by fast-forward on a maintainer approval, never by a squash. + # The author check below is the proof; the sender check is an extra guard so a human pushing + # to a bot's PR does not re-trigger approval. + # jactionlint ignore=bot-conditions if: >- contains(fromJSON('["dependabot[bot]", "supabase-cli-releaser[bot]"]'), github.event.pull_request.user.login) && contains(fromJSON('["dependabot[bot]", "supabase-cli-releaser[bot]"]'), github.event.sender.login) && From a81eba4d20402f1882e28606174b9be3a3c93e4f Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:42:19 -0700 Subject: [PATCH 14/22] ci: accept the pinned Claude Code CLI installs Generated with AI Co-Authored-By: Claude --- .github/workflows/ai-review.yml | 4 +++- .github/workflows/sync-branches.yml | 2 ++ .github/workflows/sync-repair.yml | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 74d7508729..7496a3cc69 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -160,7 +160,9 @@ jobs: # behavior mid-rollout. Installed from the trusted checkout with npm config isolation so a # PR-supplied `.npmrc` in the untrusted `pr` checkout can never redirect this install to a # hostile registry. - # 2.1.280 is the first release that accepts claude-opus-5-5. + # 2.1.280 is the first release that accepts claude-opus-5-5. The CLI stays outside the + # repository's lockfile so the untrusted checkout's dependencies never reach it. + # jactionlint ignore=adhoc-packages - name: Install Claude Code CLI working-directory: trusted run: | diff --git a/.github/workflows/sync-branches.yml b/.github/workflows/sync-branches.yml index 464af54837..4249d317f4 100644 --- a/.github/workflows/sync-branches.yml +++ b/.github/workflows/sync-branches.yml @@ -135,6 +135,7 @@ jobs: bun-version-file: trusted/.bun-version # Same pinned release and isolated npm config as the AI review workflow. + # jactionlint ignore=adhoc-packages - name: Install Claude Code CLI run: | npm install -g \ @@ -263,6 +264,7 @@ jobs: bun-version-file: trusted/.bun-version # Same pinned release and isolated npm config as the AI review workflow. + # jactionlint ignore=adhoc-packages - name: Install Claude Code CLI run: | npm install -g \ diff --git a/.github/workflows/sync-repair.yml b/.github/workflows/sync-repair.yml index 1b0c281ad1..11a2149164 100644 --- a/.github/workflows/sync-repair.yml +++ b/.github/workflows/sync-repair.yml @@ -78,6 +78,7 @@ jobs: persist-credentials: false # Same pinned release and isolated npm config as the AI review workflow. + # jactionlint ignore=adhoc-packages - name: Install Claude Code CLI if: steps.collect.outputs.action == 'repair' run: | From 136ab161d76f88dbb4e21f25d52c077b5e7231c7 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:42:21 -0700 Subject: [PATCH 15/22] ci: accept the privileged triggers the automation workflows need Each workflow exists to react to the event it is flagged for, and each one already runs only trusted-checkout code under that event. Generated with AI Co-Authored-By: Claude --- .github/workflows/ai-review.yml | 3 +++ .github/workflows/bot-pr-auto-merge.yml | 2 ++ .github/workflows/contribution-gate.yml | 2 ++ .github/workflows/live-e2e-notify.yml | 2 ++ .github/workflows/reopen-stale-issue.yml | 2 ++ .github/workflows/sync-branches.yml | 2 ++ .github/workflows/sync-repair.yml | 2 ++ 7 files changed, 15 insertions(+) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 7496a3cc69..fefd80ead4 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -16,6 +16,9 @@ on: description: "PR number to review" required: true type: string + # Comments are the /ai-review command surface; the resolve job authorizes the commenter and + # only the write job runs, from the default branch. + # jactionlint ignore=dangerous-triggers issue_comment: types: - created diff --git a/.github/workflows/bot-pr-auto-merge.yml b/.github/workflows/bot-pr-auto-merge.yml index 3c2b5d7fbd..0397f8c2a8 100644 --- a/.github/workflows/bot-pr-auto-merge.yml +++ b/.github/workflows/bot-pr-auto-merge.yml @@ -1,6 +1,8 @@ name: Bot PR auto-merge on: + # Needed for the environment secret; the job never checks out or runs pull request code. + # jactionlint ignore=dangerous-triggers pull_request_target: types: [opened, reopened, synchronize, ready_for_review] branches: [develop] diff --git a/.github/workflows/contribution-gate.yml b/.github/workflows/contribution-gate.yml index e052137ad3..8d0e86ca76 100644 --- a/.github/workflows/contribution-gate.yml +++ b/.github/workflows/contribution-gate.yml @@ -1,6 +1,8 @@ name: Contribution Gate on: + # The gate must comment on fork pull requests; it checks out the base branch only. + # jactionlint ignore=dangerous-triggers pull_request_target: types: - opened diff --git a/.github/workflows/live-e2e-notify.yml b/.github/workflows/live-e2e-notify.yml index 66edf219b7..dcd195024a 100644 --- a/.github/workflows/live-e2e-notify.yml +++ b/.github/workflows/live-e2e-notify.yml @@ -1,6 +1,8 @@ name: Live E2E Notifications on: + # Only the run's conclusion and metadata are read; nothing from the run is executed. + # jactionlint ignore=dangerous-triggers workflow_run: workflows: [Live E2E, Release] types: [completed] diff --git a/.github/workflows/reopen-stale-issue.yml b/.github/workflows/reopen-stale-issue.yml index fae44bd50c..5c21b0b77f 100644 --- a/.github/workflows/reopen-stale-issue.yml +++ b/.github/workflows/reopen-stale-issue.yml @@ -1,6 +1,8 @@ name: Reopen stale issue on: + # /reopen must work for the issue author; the script runs no code from the comment. + # jactionlint ignore=dangerous-triggers issue_comment: types: - created diff --git a/.github/workflows/sync-branches.yml b/.github/workflows/sync-branches.yml index 4249d317f4..8b3376b427 100644 --- a/.github/workflows/sync-branches.yml +++ b/.github/workflows/sync-branches.yml @@ -1,6 +1,8 @@ name: Sync branches on: + # Runs after Release on the default branch; scripts run from the trusted checkout only. + # jactionlint ignore=dangerous-triggers workflow_run: workflows: - Release diff --git a/.github/workflows/sync-repair.yml b/.github/workflows/sync-repair.yml index 11a2149164..161723717a 100644 --- a/.github/workflows/sync-repair.yml +++ b/.github/workflows/sync-repair.yml @@ -3,6 +3,8 @@ name: Sync repair # Once CI and the AI review finish on a Claude-resolved sync/develop-into-next pull request, lets Claude fix what # the merge broke and answer the review. See apps/cli/docs/release-process.md#claude-resolved-develop-into-next. on: + # Repairs the sync branch after its checks; scripts run from the trusted checkout only. + # jactionlint ignore=dangerous-triggers workflow_run: workflows: - CI From 3ece4e1723fc271111a2c74810624a5b64cc6851 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:42:22 -0700 Subject: [PATCH 16/22] ci: accept running the published installer in its own test Generated with AI Co-Authored-By: Claude --- .github/workflows/verify-install-channels.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/verify-install-channels.yml b/.github/workflows/verify-install-channels.yml index 26bfa0d90b..6978f0bc13 100644 --- a/.github/workflows/verify-install-channels.yml +++ b/.github/workflows/verify-install-channels.yml @@ -222,6 +222,9 @@ jobs: env: VERSION: ${{ inputs.version }} steps: + # Running the published installer is the point of this job; the script itself verifies the + # tarball it downloads against checksums.txt. + # jactionlint ignore=unverified-download - name: Install via the published install script shell: bash run: | From 5178f1c8d41a1532350007894a16c2d48711bddb Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:42:23 -0700 Subject: [PATCH 17/22] ci: accept the run-scoped build cache in the publish job Generated with AI Co-Authored-By: Claude --- .github/workflows/release-shared.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index 2f872d6fef..72c96a9750 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -307,6 +307,8 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} + # The key carries this run's id, so only the build job of this run can have written it. + # jactionlint ignore=cache-poisoning - name: Restore build artifacts cache uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: From 66c23aaaae7b4c960806d3f22ff79e5f62f6b005 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:42:24 -0700 Subject: [PATCH 18/22] ci: accept the tag-pinned smoke-test container image Generated with AI Co-Authored-By: Claude --- .github/workflows/setup-cli-smoke-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/setup-cli-smoke-test.yml b/.github/workflows/setup-cli-smoke-test.yml index dc9aeb1ba9..c951ce6f3c 100644 --- a/.github/workflows/setup-cli-smoke-test.yml +++ b/.github/workflows/setup-cli-smoke-test.yml @@ -84,6 +84,9 @@ jobs: # `node:20-alpine` ships a musl-linked Node, required for JavaScript-based actions to launch # here — the runner's mounted glibc Node won't execute inside an Alpine container. container: + # A moving tag is accepted: this container only hosts the installer smoke test, and a + # digest here would never be bumped automatically. + # jactionlint ignore=unpinned-images image: node:20-alpine env: VERSION: ${{ inputs.version }} From 78ad0f09e23cf54aca54d3f58fec0fdbc8e1e776 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 17:51:21 -0700 Subject: [PATCH 19/22] ci: say plainly why each jactionlint finding is accepted Generated with AI Co-Authored-By: Claude --- .github/jactionlint.yaml | 2 +- .github/workflows/ai-review.yml | 10 ++++++---- .github/workflows/apply-release-notes.yml | 8 ++++---- .github/workflows/bot-pr-auto-merge.yml | 7 ++++--- .github/workflows/contribution-gate.yml | 3 ++- .github/workflows/live-e2e-notify.yml | 2 +- .github/workflows/release-config.yml | 4 ++-- .github/workflows/release-shared.yml | 4 ++-- .github/workflows/reopen-stale-issue.yml | 5 +++-- .github/workflows/setup-cli-smoke-test.yml | 4 ++-- .github/workflows/sync-branches.yml | 3 ++- .github/workflows/sync-repair.yml | 3 ++- .github/workflows/verify-install-channels.yml | 4 ++-- 13 files changed, 33 insertions(+), 26 deletions(-) diff --git a/.github/jactionlint.yaml b/.github/jactionlint.yaml index 6bda2dc1e2..11746e71e1 100644 --- a/.github/jactionlint.yaml +++ b/.github/jactionlint.yaml @@ -12,7 +12,7 @@ self-hosted-runner: - blacksmith-8vcpu-windows-2025 - blacksmith-32vcpu-ubuntu-2404 paths: - # Git submodules of other projects; CI never checks them out. + # Other projects' repositories, pulled in as git submodules. CI never checks them out. ".repos/**": ignore: - ".*" diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index fefd80ead4..06977d74c4 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -16,8 +16,9 @@ on: description: "PR number to review" required: true type: string - # Comments are the /ai-review command surface; the resolve job authorizes the commenter and - # only the write job runs, from the default branch. + # A `/ai-review` comment on a pull request starts a review. The resolve job checks who + # commented, and the one job that can write to the pull request runs code from the default + # branch only, never from the pull request. # jactionlint ignore=dangerous-triggers issue_comment: types: @@ -163,8 +164,9 @@ jobs: # behavior mid-rollout. Installed from the trusted checkout with npm config isolation so a # PR-supplied `.npmrc` in the untrusted `pr` checkout can never redirect this install to a # hostile registry. - # 2.1.280 is the first release that accepts claude-opus-5-5. The CLI stays outside the - # repository's lockfile so the untrusted checkout's dependencies never reach it. + # 2.1.280 is the first release that accepts claude-opus-5-5. The CLI is installed on its + # own, not through the repository's lockfile, so nothing in the pull request's checkout + # can change what gets installed. # jactionlint ignore=adhoc-packages - name: Install Claude Code CLI working-directory: trusted diff --git a/.github/workflows/apply-release-notes.yml b/.github/workflows/apply-release-notes.yml index f23d5609bc..2f6c01c76b 100644 --- a/.github/workflows/apply-release-notes.yml +++ b/.github/workflows/apply-release-notes.yml @@ -27,7 +27,7 @@ jobs: outputs: authorized: ${{ steps.check.outputs.authorized }} steps: - # App token needs org membership read, and PR write to comment. + # The token reads the approver's team membership and comments on the pull request. - id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: @@ -67,9 +67,9 @@ jobs: contents: write pull-requests: write steps: - # App token needs repo write to edit the release (plus workflows write when the tagged - # commit touched .github/workflows) and to delete the branch, and PR write to comment and - # close. + # The token edits the GitHub release, deletes the branch, and comments on and closes the + # pull request. GitHub also requires the workflows permission to edit a release whose + # tagged commit changed a file under .github/workflows. - id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: diff --git a/.github/workflows/bot-pr-auto-merge.yml b/.github/workflows/bot-pr-auto-merge.yml index 0397f8c2a8..3d1006bfda 100644 --- a/.github/workflows/bot-pr-auto-merge.yml +++ b/.github/workflows/bot-pr-auto-merge.yml @@ -1,7 +1,8 @@ name: Bot PR auto-merge on: - # Needed for the environment secret; the job never checks out or runs pull request code. + # Only this event lets the job use the auto-approve environment's secret. The job never + # checks out or runs code from the pull request. # jactionlint ignore=dangerous-triggers pull_request_target: types: [opened, reopened, synchronize, ready_for_review] @@ -16,8 +17,8 @@ jobs: # Other apps (coding agents, CI vendors) also open PRs here and still need a maintainer. # Release-notes PRs are published by a maintainer approval and must never merge. Branch sync # PRs land by fast-forward on a maintainer approval, never by a squash. - # The author check below is the proof; the sender check is an extra guard so a human pushing - # to a bot's PR does not re-trigger approval. + # The first condition checks who opened the pull request. The second checks who caused this + # event, so a person pushing to a bot's pull request does not get it approved. # jactionlint ignore=bot-conditions if: >- contains(fromJSON('["dependabot[bot]", "supabase-cli-releaser[bot]"]'), github.event.pull_request.user.login) && diff --git a/.github/workflows/contribution-gate.yml b/.github/workflows/contribution-gate.yml index 8d0e86ca76..341bea8b29 100644 --- a/.github/workflows/contribution-gate.yml +++ b/.github/workflows/contribution-gate.yml @@ -1,7 +1,8 @@ name: Contribution Gate on: - # The gate must comment on fork pull requests; it checks out the base branch only. + # The gate has to comment on pull requests from forks, which only this event's token can do. + # It checks out the base branch, never the pull request. # jactionlint ignore=dangerous-triggers pull_request_target: types: diff --git a/.github/workflows/live-e2e-notify.yml b/.github/workflows/live-e2e-notify.yml index dcd195024a..441f130f15 100644 --- a/.github/workflows/live-e2e-notify.yml +++ b/.github/workflows/live-e2e-notify.yml @@ -1,7 +1,7 @@ name: Live E2E Notifications on: - # Only the run's conclusion and metadata are read; nothing from the run is executed. + # The job only reads whether the run passed. It runs nothing the run produced. # jactionlint ignore=dangerous-triggers workflow_run: workflows: [Live E2E, Release] diff --git a/.github/workflows/release-config.yml b/.github/workflows/release-config.yml index 08eb4b6a98..87e6d5e57b 100644 --- a/.github/workflows/release-config.yml +++ b/.github/workflows/release-config.yml @@ -356,8 +356,8 @@ jobs: echo "dist-tag ${NPM_TAG} -> ${VERSION} confirmed." # The CLI's install scripts and setup-cli resolve releases/latest/download/..., so a - # config release must never become the repo's "latest" release. Idempotent: skips if - # the release already exists. + # config release must never become the repo's "latest" release. Re-runs skip this step + # when the release already exists. - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index 72c96a9750..119ee14e84 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -307,7 +307,7 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - # The key carries this run's id, so only the build job of this run can have written it. + # The cache key includes this run's id, so only this run's build job can have written it. # jactionlint ignore=cache-poisoning - name: Restore build artifacts cache uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -379,7 +379,7 @@ jobs: done # Created empty; the next step uploads the assets so each one can be retried on its own. - # Idempotent: skips if the release already exists. + # Re-runs skip this step when the release already exists. - name: Create draft GitHub Release env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/reopen-stale-issue.yml b/.github/workflows/reopen-stale-issue.yml index 5c21b0b77f..1235ddbefe 100644 --- a/.github/workflows/reopen-stale-issue.yml +++ b/.github/workflows/reopen-stale-issue.yml @@ -1,7 +1,8 @@ name: Reopen stale issue on: - # /reopen must work for the issue author; the script runs no code from the comment. + # Anyone who can comment must be able to use /reopen. The script only reads the comment + # text and never runs it. # jactionlint ignore=dangerous-triggers issue_comment: types: @@ -9,7 +10,7 @@ on: permissions: contents: read - # The script only reopens, unlabels and comments; there is no narrower scope. + # The script reopens the issue, removes a label, and comments. Each needs issues: write. # jactionlint ignore=excessive-permissions issues: write diff --git a/.github/workflows/setup-cli-smoke-test.yml b/.github/workflows/setup-cli-smoke-test.yml index c951ce6f3c..1bcc37242b 100644 --- a/.github/workflows/setup-cli-smoke-test.yml +++ b/.github/workflows/setup-cli-smoke-test.yml @@ -84,8 +84,8 @@ jobs: # `node:20-alpine` ships a musl-linked Node, required for JavaScript-based actions to launch # here — the runner's mounted glibc Node won't execute inside an Alpine container. container: - # A moving tag is accepted: this container only hosts the installer smoke test, and a - # digest here would never be bumped automatically. + # The image is pinned by tag, not by digest. It only hosts the installer smoke test, and + # nothing would update a digest here automatically. # jactionlint ignore=unpinned-images image: node:20-alpine env: diff --git a/.github/workflows/sync-branches.yml b/.github/workflows/sync-branches.yml index 8b3376b427..5ab7797833 100644 --- a/.github/workflows/sync-branches.yml +++ b/.github/workflows/sync-branches.yml @@ -1,7 +1,8 @@ name: Sync branches on: - # Runs after Release on the default branch; scripts run from the trusted checkout only. + # Starts when the Release workflow finishes. Every script runs from the default-branch + # checkout, never from the merged tree. # jactionlint ignore=dangerous-triggers workflow_run: workflows: diff --git a/.github/workflows/sync-repair.yml b/.github/workflows/sync-repair.yml index 161723717a..dea2a6ec30 100644 --- a/.github/workflows/sync-repair.yml +++ b/.github/workflows/sync-repair.yml @@ -3,7 +3,8 @@ name: Sync repair # Once CI and the AI review finish on a Claude-resolved sync/develop-into-next pull request, lets Claude fix what # the merge broke and answer the review. See apps/cli/docs/release-process.md#claude-resolved-develop-into-next. on: - # Repairs the sync branch after its checks; scripts run from the trusted checkout only. + # Starts when the sync branch's checks finish. Every script runs from the default-branch + # checkout, never from the sync branch. # jactionlint ignore=dangerous-triggers workflow_run: workflows: diff --git a/.github/workflows/verify-install-channels.yml b/.github/workflows/verify-install-channels.yml index 6978f0bc13..66402ba70e 100644 --- a/.github/workflows/verify-install-channels.yml +++ b/.github/workflows/verify-install-channels.yml @@ -222,8 +222,8 @@ jobs: env: VERSION: ${{ inputs.version }} steps: - # Running the published installer is the point of this job; the script itself verifies the - # tarball it downloads against checksums.txt. + # This job exists to run the published install script. That script checks the tarball it + # downloads against the release's checksums.txt before using it. # jactionlint ignore=unverified-download - name: Install via the published install script shell: bash From b8087a56379c36500698eab5938f13b45f2d902d Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 18:02:08 -0700 Subject: [PATCH 20/22] ci: read issues with the release-notes proposal token Creating the label works with pull-requests write; the only other issue access is Claude reading linked issues. Generated with AI Co-Authored-By: Claude --- .github/workflows/propose-release-notes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/propose-release-notes.yml b/.github/workflows/propose-release-notes.yml index 3fbf0d9136..3ba3ad70fc 100644 --- a/.github/workflows/propose-release-notes.yml +++ b/.github/workflows/propose-release-notes.yml @@ -62,7 +62,7 @@ jobs: client-id: ${{ vars.GH_APP_CLIENT_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} permission-contents: write - permission-issues: write + permission-issues: read permission-pull-requests: write - uses: useblacksmith/checkout@6fd481652155169ed4d2f25ebaf97464f685175f # v1 From 37bca2c18cdbe0ee4bda70b171b35d1aea07d385 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 18:17:04 -0700 Subject: [PATCH 21/22] ci: drop the resolver's head_ref output with the job output Generated with AI Co-Authored-By: Claude --- .github/scripts/ai-review/resolve.ts | 3 +-- .github/workflows/ai-review.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/scripts/ai-review/resolve.ts b/.github/scripts/ai-review/resolve.ts index 0cfaafc071..74984e22ed 100644 --- a/.github/scripts/ai-review/resolve.ts +++ b/.github/scripts/ai-review/resolve.ts @@ -12,7 +12,7 @@ * * `resolveDecision` is the pure orchestration function (I/O injected) a test * can drive without the network; `main()` wires up the real GitHub I/O, - * writes `should_run`/`pr_number`/`head_ref`/`trigger` to `$GITHUB_OUTPUT`, + * writes `should_run`/`pr_number`/`head_sha`/`trigger` to `$GITHUB_OUTPUT`, * and surfaces the skip reason (if any) in `$GITHUB_STEP_SUMMARY`. * * Run in CI as: `bun .github/scripts/ai-review/resolve.ts`. @@ -374,7 +374,6 @@ function writeOutputs(result: ResolveResult, prNumber: number): void { const entries: Record = { should_run: String(result.shouldRun), pr_number: String(prNumber), - head_ref: `refs/pull/${prNumber}/head`, head_sha: result.headSha ?? "", trigger: result.trigger, }; diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 06977d74c4..56bbfb6926 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -550,7 +550,7 @@ jobs: steps: # Security-critical: this is the only job with write permission, so it must only ever # execute trusted base-branch code — never the PR head. Checking out `develop` explicitly - # (never `needs.resolve.outputs.head_ref`) keeps a malicious PR from smuggling a script + # (never the PR's own ref) keeps a malicious PR from smuggling a script # change into the one job that can write back to the PR. (For `pull_request` events GitHub # runs the workflow file from the PR's own ref; acceptable since the auto path only admits # same-repo PRs, whose authors hold write access anyway, and fork PRs run with a read-only From f4d989f5f4dade2b46512f4c9d3c0c9d602fc0cc Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 22 Oct 2026 18:25:32 -0700 Subject: [PATCH 22/22] ci: keep the workflows scope off the release-notes apply token The apply job runs the notes script from the pull request's checkout, so its token must not be able to change workflow files. Releases for tags on main are already created and edited with the workflow token, which can never hold that scope, so editing the notes does not need it either. Generated with AI Co-Authored-By: Claude --- .github/workflows/apply-release-notes.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/apply-release-notes.yml b/.github/workflows/apply-release-notes.yml index 2f6c01c76b..831ec66cf7 100644 --- a/.github/workflows/apply-release-notes.yml +++ b/.github/workflows/apply-release-notes.yml @@ -68,15 +68,14 @@ jobs: pull-requests: write steps: # The token edits the GitHub release, deletes the branch, and comments on and closes the - # pull request. GitHub also requires the workflows permission to edit a release whose - # tagged commit changed a file under .github/workflows. + # pull request. The script below runs from the pull request's own checkout, so the token + # must not be able to change workflow files. - id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.GH_APP_CLIENT_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} permission-contents: write - permission-workflows: write permission-pull-requests: write # Checkout the PR head so reviewer edits made before approval are captured;