From 942a35c863522436082bf3339c777bee4581af83 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 09:31:29 +0000 Subject: [PATCH] Let outside contributors get a green check list The autolabeler went red on every pull request from a fork: "Resource not accessible by integration" adding the label it had already worked out. Nothing in the pull request caused it and nothing in the pull request could clear it, so #91 could not be merged on a clean run. A `pull_request` run on a fork's head gets a read-only GITHUB_TOKEN, because the code it is about to execute is a stranger's, and `permissions:` cannot undo that - it only drops privileges. So `pull-requests: write` was a request GitHub was always going to refuse. The label comes from the API alone: no checkout, no install, no contributor code in the job. So it moves to `pull_request_target`, which runs from `main` with a token that can write and reads release-drafter.yml from `main` rather than from the contributor's branch. The action is pinned to the commit behind v7 instead of the tag, now that it resolves inside a job holding that token - the reasoning that already pins aws-iam-authenticator by checksum. The rule that keeps it safe is stated in the file: never check out, install or execute pull request code here. Nothing else changed. ci.yaml already builds without secrets - `sign: false` on pull requests keeps the Developer ID out of a job that has run `npm ci` from the branch - so a fork gets lint, the end-to-end tests and an unsigned six-row build. AGENTS.md now records that, and the two repository settings that would clear a red fork check by handing fork code the signing keys. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FCijaTvChWKvAGFRUoqBzF --- .github/workflows/autolabeler.yaml | 18 ++++++++++++++++-- AGENTS.md | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/autolabeler.yaml b/.github/workflows/autolabeler.yaml index 964f95d..a1afe45 100644 --- a/.github/workflows/autolabeler.yaml +++ b/.github/workflows/autolabeler.yaml @@ -3,15 +3,27 @@ name: 🏷️ Autolabel Pull Requests # Applies release-drafter labels (feature/fix/chore) to incoming PRs based on # branch-name patterns from .github/release-drafter.yml. Labels feed into the # changelog categories that release-drafter.yaml later groups for the draft. +# +# `pull_request_target`, not `pull_request`: a `pull_request` run on a fork gets +# a read-only GITHUB_TOKEN whatever `permissions:` asks for - that block only +# drops privileges - so labelling failed on every outside contribution and left +# a red check nobody could clear. This runs from `main` with a token that can +# write, and reads the config from `main` too. +# +# That token is why: **never check out, install or execute pull request code +# here.** One checkout of the PR ref plus one `npm ci` hands write access to a +# stranger's postinstall script. This job talks to the API and nothing else. +# Anything that runs contributor code belongs in ci.yaml. on: - pull_request: + pull_request_target: types: - opened - reopened - synchronize permissions: + contents: read pull-requests: write jobs: @@ -19,7 +31,9 @@ jobs: name: 🏷️ Autolabel pull request runs-on: ubuntu-latest steps: + # Pinned to the commit behind v7: tags are mutable, and this one now + # resolves inside a job holding a write token. - name: 🏷️ Run autolabeler - uses: release-drafter/release-drafter/autolabeler@v7 + uses: release-drafter/release-drafter/autolabeler@34d80673e067bdc0c24568d3af899c216adcfaa9 # v7 with: config-name: release-drafter.yml diff --git a/AGENTS.md b/AGENTS.md index a971173..bbc2b37 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -479,6 +479,21 @@ adding packager `ignore` rules. - **`pages.yaml`** — publishes `docs/` on pushes to `main` touching `docs/**`. - **`autolabeler.yaml`** — applies changelog labels from branch-name patterns (`feat/`, `fix/`, `chore/`). Any other prefix needs labels set by hand. + On **`pull_request_target`**, and it must stay there: a `pull_request` run on + a fork gets a read-only token whatever `permissions:` asks for. The trade is + a write token, so **it must never check out, install or run pull request + code**, and its action is pinned to a commit, not a tag. + +**Pull requests from forks** get no secrets and a read-only token, by design. +`ci.yaml` runs contributor code, so it stays on `pull_request`, and `sign: +false` keeps the Developer ID out of a job that runs it. Lint, the end-to-end +tests and an unsigned six-row build are what a fork gets — enough to merge on. +Two repository settings would clear a red fork check by handing fork code the +keys: *send write tokens to* and *send secrets and variables to workflows from +fork pull requests*. Leave both off; a job needing a write token gets its own +checkout-free `pull_request_target` workflow instead. The separate *require +approval* gate, where a fork's run waits for a maintainer to press **Approve +and run** once per push, is expected, not a failure. **Version numbers come from PR labels, not `package.json`.** The release-drafter resolver reads `major`/`minor`/`patch` off merged PRs and