Summary
examples/claude-code-review.yml's dispatch-on-comment job declares two permission scopes and then reads a pull request, which needs a third it does not declare.
permissions:
actions: write # dispatch this workflow via `gh workflow run`
issues: write # acknowledge the /review comment
The step below it does:
PR_JSON=$(gh api "repos/$REPO/pulls/$PR_NUMBER")
PR_BRANCH=$(jq -r '.head.ref' <<< "$PR_JSON")
PR_HEAD_REPO=$(jq -r '.head.repo.full_name' <<< "$PR_JSON")
Declaring any permissions: block sets every unlisted scope to no-access (metadata excepted), so pull-requests is explicitly denied here rather than defaulted.
Why it does not fail loudly today
Most consumers of this stub are public repos, where an under-scoped installation token can generally still read public pull-request metadata. So the call succeeds and the gap stays invisible.
That makes it a latent rather than a live bug, and worth fixing for two reasons:
- A private consumer would break, at the
gh api call, with a 404 that reads like a bad PR number rather than a permissions problem. set -euo pipefail is active, so the job dies there and the /review comment is never acknowledged --- the user sees nothing.
- The stub is the documented copy-paste artifact. Every repo that adopts
/review inherits the gap, and the permissions: blocks in examples/ are exactly what the README tells people to copy as-is ("The stubs in examples/ already include the right permissions: blocks -- copy them as-is").
Suggested fix
One line in examples/claude-code-review.yml:
permissions:
actions: write # dispatch this workflow
issues: write # acknowledge the /review comment
pull-requests: read # read the PR's head branch and head repo
Worth a sweep of the other example stubs for the same shape while in there --- any stub whose job declares a permissions: block and then calls gh api repos/.../pulls/... or gh pr view has the same latent gap.
Provenance
Found by an adversarial review of a consumer migration adopting this stub (UCD-SERG/ucd-serg.github.io#110). Fixed consumer-side there; filing upstream so the next adopter does not inherit it.
Summary
examples/claude-code-review.yml'sdispatch-on-commentjob declares two permission scopes and then reads a pull request, which needs a third it does not declare.The step below it does:
Declaring any
permissions:block sets every unlisted scope to no-access (metadata excepted), sopull-requestsis explicitly denied here rather than defaulted.Why it does not fail loudly today
Most consumers of this stub are public repos, where an under-scoped installation token can generally still read public pull-request metadata. So the call succeeds and the gap stays invisible.
That makes it a latent rather than a live bug, and worth fixing for two reasons:
gh apicall, with a 404 that reads like a bad PR number rather than a permissions problem.set -euo pipefailis active, so the job dies there and the/reviewcomment is never acknowledged --- the user sees nothing./reviewinherits the gap, and thepermissions:blocks inexamples/are exactly what the README tells people to copy as-is ("The stubs inexamples/already include the rightpermissions:blocks -- copy them as-is").Suggested fix
One line in
examples/claude-code-review.yml:Worth a sweep of the other example stubs for the same shape while in there --- any stub whose job declares a
permissions:block and then callsgh api repos/.../pulls/...orgh pr viewhas the same latent gap.Provenance
Found by an adversarial review of a consumer migration adopting this stub (UCD-SERG/ucd-serg.github.io#110). Fixed consumer-side there; filing upstream so the next adopter does not inherit it.