Skip to content

Commit c6f090a

Browse files
CIclaude
andcommitted
Triage bot: pass review comment bodies as files, not inline arguments
PR #5229's /code-review found seven issues and posted none of them. Its POST was endpoint-first, which the scoped gh api allowlist covers, but the body went in as a double-quoted -f argument full of backticks. The shell reads those as command substitution, so the permission check saw extra commands no rule allows and dontAsk denied the whole call. Nearly every review comment quotes code, so this blocks almost every review. GH_API_ENDPOINT_FIRST_PROMPT now tells the agent to write each body to the scratch directory and pass it with -F body=@<file>, and its worked example does the same. No allowlist change: both flows that append the prompt (review and cleanup) already hold Write and the scratch Edit scope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 130596b commit c6f090a

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

‎tools/test_triage_daemon.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,18 @@ def test_tells_the_agent_to_report_a_denial_rather_than_print_findings(self):
741741
completed review in the log. The prompt asks for a denial to be stated plainly."""
742742
self.assertIn("denied", triage_daemon.GH_API_ENDPOINT_FIRST_PROMPT)
743743

744+
def test_steers_comment_bodies_into_a_scratch_file(self):
745+
"""PR #5229's review had its endpoint-first POST denied because the body was an inline
746+
double-quoted argument holding backticks: the shell reads those as command substitution,
747+
so the permission check saw extra commands no rule allows. A body read from a file with
748+
`-F body=@<file>` never touches shell quoting, so the prompt must ask for exactly that,
749+
in the scratch directory the flow is allowed to write, and the worked example must use it."""
750+
prompt = triage_daemon.GH_API_ENDPOINT_FIRST_PROMPT
751+
self.assertIn("-F body=@", prompt)
752+
self.assertIn(str(triage_daemon.SCRATCH_DIR), prompt)
753+
self.assertIn("backticks", prompt)
754+
self.assertNotIn("-f body=", prompt)
755+
744756
def test_requires_disclosure_on_every_posted_comment_or_reply(self):
745757
"""/code-review's own instructions live in a skill we don't own, so this appended
746758
prompt is the only lever available to make its inline comments disclose they're

‎tools/triage_daemon.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,10 @@
451451
# above: the allowlist covers the spellings we enumerated, this keeps the agent on the one
452452
# spelling that is certain to be covered, and asks it to say so loudly when a call is denied
453453
# anyway - #4758 quietly degraded to printing the comments it could not post, which reads like
454-
# a finished review in the log. Also carries the bot-disclosure requirement for these two flows:
454+
# a finished review in the log. It also moves comment bodies into a scratch file: PR #5229's
455+
# POSTs were endpoint-first and still denied, because a double-quoted body holding backticks is
456+
# command substitution to the shell, and the permission check denies the substituted commands
457+
# that no rule allows. Also carries the bot-disclosure requirement for these two flows:
455458
# /code-review's own instructions live in a skill we don't own, so this prompt is the only
456459
# lever available for it; /pr-cleanup's SKILL.md already asks for disclosure directly, and
457460
# this is the belt-and-braces backup for it, same reasoning as the endpoint-first steer.
@@ -507,8 +510,11 @@
507510
GH_API_ENDPOINT_FIRST_PROMPT = (
508511
"Permission rules in this session match a literal command prefix, so `gh api` calls are only permitted when the current allowlist covers the exact spelling you use. "
509512
"Prefer the endpoint-first, unquoted form (endpoint immediately after `gh api`) and put flags after the endpoint - for example "
510-
f"`gh api repos/{REPO}/pulls/123/comments --method POST -f path=apps/predbat/example.py`. "
513+
f"`gh api repos/{REPO}/pulls/123/comments --method POST -f path=apps/predbat/example.py -F body=@{SCRATCH_DIR}/comment-1.md`. "
511514
'Other spellings (e.g. `gh api --method POST repos/...`, `gh api -X POST repos/...`, `gh api -H ... repos/...` or `gh api "repos/..."`) may be denied in restricted sessions even when the same request is allowed in endpoint-first form. '
515+
f"Never put a comment body on the command line: write it to a file in {SCRATCH_DIR} with the Write tool first and pass it as `-F body=@<file>`. "
516+
"An inline body in double quotes turns any backticks or `$(...)` in it into command substitution, which the permission check "
517+
"sees as extra commands and denies - that is what blocked every comment in PR #5229's review. "
512518
"Keep each call to a single command: piping into head/tail/grep is fine, but redirecting output anywhere outside "
513519
f"{SCRATCH_DIR} or the repository clone - /tmp included - is denied as well. "
514520
"If a call is denied regardless, state that plainly in your final message and name the command; do not quietly fall back "

0 commit comments

Comments
 (0)