Summary
claude.yml's default claude-args allows Bash(gh api:*) and denies three prefix patterns:
Bash(gh api -X:*),Bash(gh api --method:*),Bash(gh api graphql:*)
The inline comment states the intent:
gh api is allowed for the read-only polling loop, but its write forms (-X/--method mutations and graphql) are denied so a write-scoped GITHUB_TOKEN can't be used for arbitrary API mutations
The deny list does not implement that intent. Two independent gaps, and the agent runs with contents: write, issues: write, pull-requests: write, and actions: write.
Gap 1: a POST needs no -X
gh api --help:
The default HTTP request method is GET normally and POST if any parameters were added. Override the method with --method.
Verified against the installed gh on 2026-08-24. So every -f / -F / --input call is a POST that matches the gh api:* allow and none of the three deny patterns. The CLI's own help text uses exactly this form as its worked example -- an issue-comment POST written with -f body=... and no -X anywhere in it.
Gap 2: the patterns are prefix matches, so argument order defeats them
Bash(gh api -X:*) matches only a command beginning gh api -X. Reordering is enough: putting a -H flag first and -X POST second produces an equivalent request that matches no deny pattern, since gh accepts flags in any order.
Why it matters
The comment presents the denials as the reason a write-scoped token is safe, and the same block explains that git push is handled by omission rather than denial precisely because enumerating deny patterns is fragile:
That sidesteps the whole class of destructive push variants rather than enumerating deny patterns
gh api then does the enumerating-deny-patterns thing anyway, on a surface with more variants than git push.
The exposure is bounded by the trusted-author gate, which the same comment already names as "the primary containment" -- so this is not a break-glass hole. It is a stated defense-in-depth control that does not hold, which is worth either fixing or restating.
Options
- Drop
Bash(gh api:*) from the allow list and enumerate the read paths the polling loop actually needs. Matches how git push is already handled -- omission over denial. Most robust, most work.
- Keep the allow and fix the wording, stating that
gh api writes are bounded by the trusted-author gate rather than by the deny list. Cheapest, and honest.
- Leave the patterns as a speed bump but stop describing them as denying "write forms".
Option 1 is the real fix; option 2 is worth doing immediately either way, since the current comment would let a reader conclude the control exists.
Provenance
Found by an adversarial review of a consumer migration adopting this workflow (UCD-SERG/ucd-serg.github.io#111), while fact-checking a comment in the caller stub that paraphrased this one. The consumer-side comment has been corrected to describe what is actually denied.
Summary
claude.yml's defaultclaude-argsallowsBash(gh api:*)and denies three prefix patterns:The inline comment states the intent:
The deny list does not implement that intent. Two independent gaps, and the agent runs with
contents: write,issues: write,pull-requests: write, andactions: write.Gap 1: a POST needs no
-Xgh api --help:Verified against the installed
ghon 2026-08-24. So every-f/-F/--inputcall is a POST that matches thegh api:*allow and none of the three deny patterns. The CLI's own help text uses exactly this form as its worked example -- an issue-comment POST written with-f body=...and no-Xanywhere in it.Gap 2: the patterns are prefix matches, so argument order defeats them
Bash(gh api -X:*)matches only a command beginninggh api -X. Reordering is enough: putting a-Hflag first and-X POSTsecond produces an equivalent request that matches no deny pattern, sinceghaccepts flags in any order.Why it matters
The comment presents the denials as the reason a write-scoped token is safe, and the same block explains that
git pushis handled by omission rather than denial precisely because enumerating deny patterns is fragile:gh apithen does the enumerating-deny-patterns thing anyway, on a surface with more variants thangit push.The exposure is bounded by the trusted-author gate, which the same comment already names as "the primary containment" -- so this is not a break-glass hole. It is a stated defense-in-depth control that does not hold, which is worth either fixing or restating.
Options
Bash(gh api:*)from the allow list and enumerate the read paths the polling loop actually needs. Matches howgit pushis already handled -- omission over denial. Most robust, most work.gh apiwrites are bounded by the trusted-author gate rather than by the deny list. Cheapest, and honest.Option 1 is the real fix; option 2 is worth doing immediately either way, since the current comment would let a reader conclude the control exists.
Provenance
Found by an adversarial review of a consumer migration adopting this workflow (UCD-SERG/ucd-serg.github.io#111), while fact-checking a comment in the caller stub that paraphrased this one. The consumer-side comment has been corrected to describe what is actually denied.