diff --git a/.github/workflows/update-lang-fix-psr-and-sync-version.yml b/.github/workflows/update-lang-fix-psr-and-sync-version.yml index 5cf3c8f..279ada9 100644 --- a/.github/workflows/update-lang-fix-psr-and-sync-version.yml +++ b/.github/workflows/update-lang-fix-psr-and-sync-version.yml @@ -114,7 +114,28 @@ concurrency: cancel-in-progress: false permissions: - contents: write + # `read`, and deliberately not `write`, because nothing here uses the ambient + # GITHUB_TOKEN any more: the checkout, the commit/push and the version badge + # all run on GitHub App tokens. What this line really controls now is who can + # CALL this workflow. + # + # A reusable workflow's `permissions:` is a REQUEST, and GitHub refuses the + # call outright when a caller grants less -- it is a hard startup error, not a + # silent capping: + # + # The workflow is requesting 'contents: write', but is only allowed + # 'contents: read'. + # + # No jobs run, no logs are produced, and the run just shows "a workflow file + # issue" -- which is how fogproject's merge stub failed on its first two real + # merges while asking for the correct, least-privilege `contents: read`. + # + # So do not raise this back to `write` to be safe. Raising it forces every + # caller to grant write for a token none of them use, and breaks at startup + # any caller that does not. If a step here ever genuinely needs the ambient + # token, give that step its own App token instead, the way the badge step + # does. + contents: read env: # Pinned, not "latest". An unpinned formatter is a second source of drift: diff --git a/CLAUDE.md b/CLAUDE.md index df9db1e..b6d0e60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -179,6 +179,12 @@ don't "simplify" those back to the default `GITHUB_TOKEN` just because the call `workflow_call`/`workflow_dispatch`, and `pull_request: types: [closed]` (a merge — which the bot's direct push is not) are all safe for the same reason, and all three are in use. The test to apply to a new trigger is that one question, not whether it happens to be a cron. +- A reusable workflow's `permissions:` block is a **request**, and a caller granting less is a + hard startup error — `The workflow is requesting 'contents: write', but is only allowed + 'contents: read'` — not a silent capping. No jobs run and no logs are written, so it surfaces + only as "a workflow file issue". Keep a reusable workflow's request as low as it genuinely + needs (these workflows do their writing with App tokens, so `contents: read` is usually + right); raising it forces every caller to grant the same, and breaks the ones that don't. - Separately from that safety question, check **which ref a trigger is read from** before relying on it. Most events — including `pull_request_target`, `schedule` and `workflow_dispatch` — are read only from the repository's default branch, so a workflow file