diff --git a/.github/actions/detect-changes/action.yml b/.github/actions/detect-changes/action.yml index 7d95ba76c9a2..0794b5ed4f8e 100644 --- a/.github/actions/detect-changes/action.yml +++ b/.github/actions/detect-changes/action.yml @@ -7,7 +7,7 @@ description: >- inputs: github-token: - description: Token for the GitHub API (gh CLI). Pass secrets.AUTOFIX_BOT_PAT from the calling workflow. + description: Token for the GitHub API (gh CLI). Pass steps.app-token.outputs.token from the calling workflow. required: false default: ${{ github.token }} diff --git a/.github/actions/get-app-token/action.yml b/.github/actions/get-app-token/action.yml new file mode 100644 index 000000000000..d42e46e81364 --- /dev/null +++ b/.github/actions/get-app-token/action.yml @@ -0,0 +1,59 @@ +name: Get GitHub App Token +description: >- + Mint a short-lived (1-hour) installation access token from the repo's + GitHub App, replacing the long-lived AUTOFIX_BOT_PAT. App tokens get + 5,000 req/hr per installation (vs 1,000 for the default GITHUB_TOKEN) + and are scoped to the App's installation permissions, not a user account. + + Falls back to the built-in GITHUB_TOKEN when APP_ID is not set — this + happens on fork PRs where repo secrets are unavailable. The fallback + ensures classification, timings, and review comments still work on + forks (with the lower GITHUB_TOKEN rate limit). + + Requires two repo secrets (store when creating the App): + - APP_ID — the App's numeric ID (Settings → General) + - APP_PRIVATE_KEY — the PEM private key (Settings → Private keys) + + The App must be installed on the repository (or org) with the + permissions the calling workflow needs. + +inputs: + owner: + description: Repository owner (for cross-org tokens). Defaults to the current repo's owner. + required: false + default: ${{ github.repository_owner }} + +outputs: + token: + description: A GitHub App installation access token (1-hour TTL), or GITHUB_TOKEN on forks. + value: ${{ steps.app-token.outputs.token || steps.fallback.outputs.token }} + +runs: + using: composite + steps: + - name: Check if App credentials exist + id: check + shell: bash + env: + APP_ID: ${{ secrets.APP_ID }} + run: | + if [ -n "$APP_ID" ]; then + echo "has_app=true" >> "$GITHUB_OUTPUT" + else + echo "has_app=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create GitHub App token + id: app-token + if: steps.check.outputs.has_app == 'true' + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: ${{ inputs.owner }} + + - name: Fall back to GITHUB_TOKEN + id: fallback + if: steps.check.outputs.has_app != 'true' + shell: bash + run: echo "token=${{ github.token }}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96ff02597f0e..d22dc3381536 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,13 +49,16 @@ jobs: event_name: ${{ github.event_name }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token - name: Detect affected areas id: classify uses: ./.github/actions/detect-changes with: - # Forks get no repo secrets (AUTOFIX_BOT_PAT is empty); fall back to - # the built-in read-only token so classification still works there. - github-token: ${{ secrets.AUTOFIX_BOT_PAT || github.token }} + # The get-app-token composite action falls back to GITHUB_TOKEN + # on fork PRs where APP_ID is unavailable. + github-token: ${{ steps.app-token.outputs.token }} # ───────────────────────────────────────────────────────────────────── # Lane-gated sub-workflows. Each runs in parallel after detect finishes. @@ -318,6 +321,10 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token + - name: Restore baseline cache (PR only) if: github.event_name == 'pull_request' uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -334,7 +341,9 @@ jobs: # Forks get no repo secrets (AUTOFIX_BOT_PAT is empty); fall back to # the built-in read-only token so the timings API read still works # there instead of hard-failing this advisory job on every fork PR. - GITHUB_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT || github.token }} + # The get-app-token composite action falls back to GITHUB_TOKEN + # on fork PRs where APP_ID is unavailable. + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} run: | python3 scripts/ci/timings_report.py \ --baseline ci-timings-baseline.json \ diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index e06a0842a633..f03dbf8ec84e 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -56,6 +56,10 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 @@ -73,8 +77,8 @@ jobs: - name: Prepare skills index (unified multi-source catalog) env: - GH_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} - GITHUB_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} SKILLS_INDEX_RUN_ID: ${{ github.event.inputs.skills_index_run_id || '' }} REBUILD_SKILLS_INDEX: ${{ github.event.inputs.rebuild_skills_index || 'false' }} run: | diff --git a/.github/workflows/js-autofix.yml b/.github/workflows/js-autofix.yml index 38494abd0e38..be8b8b4473a7 100644 --- a/.github/workflows/js-autofix.yml +++ b/.github/workflows/js-autofix.yml @@ -7,7 +7,7 @@ name: auto-fix lint issues & formatting # auto-corrected on merge so PRs aren't blocked by them. The PR-time eslint # check in typecheck.yml fails only when un-fixable errors remain. # -# NOTE: AUTOFIX_BOT_PAT pushes DO trigger further workflow runs (unlike +# NOTE: App token pushes DO trigger further workflow runs (unlike # secrets.GITHUB_TOKEN). The concurrency group (ts-autofix-${{ github.ref }}) # with cancel-in-progress: true prevents an infinite loop — a re-triggered # run cancels the in-flight one, and since the second run finds no new fixes @@ -128,6 +128,10 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token + - name: Download patch uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -170,7 +174,7 @@ jobs: - name: Create/update PR and enable auto-merge env: - GH_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} BOT_BRANCH: bot/js-autofix run: | set -euo pipefail @@ -193,7 +197,7 @@ jobs: - name: Wait for merge, auto-close on failure or stale env: - GH_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} START_SHA: ${{ github.sha }} run: | set -euo pipefail diff --git a/.github/workflows/skills-index-freshness.yml b/.github/workflows/skills-index-freshness.yml index 5a9bf98a0f46..70fc5c6da28e 100644 --- a/.github/workflows/skills-index-freshness.yml +++ b/.github/workflows/skills-index-freshness.yml @@ -108,10 +108,15 @@ jobs: echo "Summary: ${{ steps.probe.outputs.summary }}" fi + - name: Get GitHub App token + if: steps.probe.outputs.status != 'ok' + id: app-token + uses: ./.github/actions/get-app-token + - name: Open issue on degraded / failed probe if: steps.probe.outputs.status != 'ok' env: - GH_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} STATUS: ${{ steps.probe.outputs.status }} DETAIL: ${{ steps.probe.outputs.detail }} run: | diff --git a/.github/workflows/skills-index.yml b/.github/workflows/skills-index.yml index 8930a636fc08..5f8259b278c8 100644 --- a/.github/workflows/skills-index.yml +++ b/.github/workflows/skills-index.yml @@ -24,6 +24,10 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.11" @@ -35,7 +39,7 @@ jobs: - name: Build skills index env: - GITHUB_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} run: python scripts/build_skills_index.py - name: Upload index artifact @@ -54,7 +58,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token - name: Trigger Deploy Site workflow env: - GH_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: gh workflow run deploy-site.yml --repo ${{ github.repository }} -f skills_index_run_id=${{ github.run_id }} diff --git a/.github/workflows/supply-chain-audit.yml b/.github/workflows/supply-chain-audit.yml index e8a4508328f8..f4ba220917be 100644 --- a/.github/workflows/supply-chain-audit.yml +++ b/.github/workflows/supply-chain-audit.yml @@ -60,10 +60,14 @@ jobs: with: fetch-depth: 0 + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token + - name: Scan diff for critical patterns id: scan env: - GH_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | set -euo pipefail diff --git a/.github/workflows/upload_to_pypi.yml b/.github/workflows/upload_to_pypi.yml index de21ce473a4e..1c56d2978ca1 100644 --- a/.github/workflows/upload_to_pypi.yml +++ b/.github/workflows/upload_to_pypi.yml @@ -143,9 +143,13 @@ jobs: name: python-package-distributions path: dist/ + - name: Get GitHub App token + id: app-token + uses: ./.github/actions/get-app-token + - name: Wait for GitHub Release to exist env: - GITHUB_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # release.py creates the GitHub Release after pushing the tag, # but this workflow starts from the tag push — wait for it. run: | @@ -171,7 +175,7 @@ jobs: - name: Attach signed artifacts to GitHub Release if: env.skip_sign != 'true' env: - GITHUB_TOKEN: ${{ secrets.AUTOFIX_BOT_PAT }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # release.py already created the GitHub Release — just upload # the Sigstore signatures alongside the existing assets. run: >-