State the Checks API gap instead of prescribing an impossible tick

Fine-grained tokens have no Checks permission. GitHub's permission reference
lists no Checks section and no check-run endpoint, and checks is absent from
the token form's pre-fill parameters, so the earlier instruction to tick
Checks: Read asked for a box that does not exist. A token created with every
listed permission still could not read check runs, which is what surfaced this.

The prompt now states the consequence rather than offering a remedy: gh pr
checks reports commit statuses only and gh run view returns no annotations.
Both degrade to empty output rather than a permission error, so without the
note they read as a broken CI integration. Job logs are unaffected; they fall
under Actions, which is granted.

secret_scanning_alerts and vulnerability_alerts move into the pre-filled URL,
leaving nothing for the operator to tick beyond repository selection.
This commit is contained in:
2026-07-31 08:48:26 -05:00
parent b8bf9f9eff
commit 5e167d3a0d
3 changed files with 47 additions and 26 deletions
+23 -8
View File
@@ -186,15 +186,30 @@ workflows=write actions=write statuses=read security_events=write
secret_scanning_alerts=read vulnerability_alerts=read
```
Two things the form cannot pre-fill, so the task prints them as a checklist:
One thing the form cannot pre-fill: **Repository access → Only select repositories
→ `api-portal`.** GitHub has no query parameter for repository selection.
1. **Repository access → Only select repositories → `api-portal`.** GitHub has no
query parameter for repository selection.
2. **Checks: Read.** The only permission the agent needs that is absent from
GitHub's pre-fill parameter list. Without it `gh pr checks` reports no status
rollup and `gh run view` returns no annotations — both fail as empty results
rather than as permission errors, so a missed tick is easy to misread as a
broken CI integration.
### The Checks API is out of reach
Fine-grained tokens cannot read check runs. This is not a permission you forgot to
grant — GitHub's permission reference has no Checks section and lists no check-run
endpoint, so there is no box to tick. Inside the sandbox:
| Command | Behaviour |
| --- | --- |
| `gh pr checks` | shows commit statuses only, not check runs |
| `gh run view` | returns no annotations |
| `gh run view --log` | works — job logs fall under Actions |
Both degrade to empty output rather than a permission error, so they read as a broken
CI integration unless you know why. A 403 names what it wanted in the
`X-Accepted-GitHub-Permissions` response header.
Only an installation token from a GitHub App can reach the Checks API. That was
evaluated and rejected for this workflow: minting one requires the App private key on
every developer's machine, and device-flow user tokens — the alternative that needs no
key — were measured and do **not** honour `repository_id`, so they reach every
repository in the installation.
Generate the token and paste it at the prompt. It is read with the terminal echo off
and piped straight into the `sbx` secret store, so it never reaches your shell history.
+17 -14
View File
@@ -40,13 +40,13 @@ TOKEN_URL_PERMISSIONS=(
vulnerability_alerts=read
)
# "checks" is the one permission the agent needs that GitHub omits from the
# pre-fill parameters, so it has to be ticked by hand. Each entry names what
# breaks without it, because an unticked box fails later as an empty result or
# a 403 rather than as a permission error.
TOKEN_MANUAL_PERMISSIONS=(
"Checks: Read - without it 'gh pr checks' reports no status rollup and"
" 'gh run view' returns no annotations"
# Fine-grained tokens cannot reach the Checks API at all: GitHub's permission
# reference has no Checks section and lists no check-run endpoint, so there is
# no box to tick. Both calls below degrade to empty output rather than a
# permission error, which reads as broken CI unless it is called out.
TOKEN_LIMITATIONS=(
"gh pr checks shows only commit statuses, not check runs"
"gh run view returns no annotations"
)
usage() {
@@ -205,17 +205,20 @@ Create a fine-grained token for $REPOSITORY.
Everything except the repository is pre-filled. On the page:
1. Repository access -> Only select repositories -> ${REPOSITORY#*/}
2. Under Permissions -> Repository permissions, tick the one the form
cannot pre-fill:
2. Generate token, then paste it below.
Every permission is pre-filled. Fine-grained tokens cannot read check runs,
so inside the sandbox:
EOF
local permission
for permission in "${TOKEN_MANUAL_PERMISSIONS[@]}"; do
printf ' %s\n' "$permission" >&2
local limitation
for limitation in "${TOKEN_LIMITATIONS[@]}"; do
printf ' %s\n' "$limitation" >&2
done
cat >&2 <<EOF
3. Generate token, then paste it below.
cat >&2 <<'EOF'
A 403 will name what it wanted in the X-Accepted-GitHub-Permissions header.
EOF
+7 -4
View File
@@ -69,11 +69,14 @@ for expected in secret_scanning_alerts=read vulnerability_alerts=read statuses=r
fail "permission dropped out of the pre-filled URL: $expected"
done
((${#TOKEN_MANUAL_PERMISSIONS[@]})) ||
fail "the manual checklist is empty; checks is not pre-fillable and must be listed"
((${#TOKEN_LIMITATIONS[@]})) ||
fail "the limitations list is empty; the Checks gap must be stated"
printf '%s\n' "${TOKEN_MANUAL_PERMISSIONS[@]}" | grep -q 'Checks' ||
fail "the manual checklist must name Checks"
printf '%s\n' "${TOKEN_LIMITATIONS[@]}" | grep -q 'gh pr checks' ||
fail "the limitations must name gh pr checks"
printf '%s\n' "${TOKEN_LIMITATIONS[@]}" | grep -qi 'tick\|check the box' &&
fail "the limitations must not imply Checks can be granted"
if ((failures)); then
printf '%d assertion(s) failed\n' "$failures" >&2