Browse Source

fix(ci): let the PR review save its body, and scope the write-check bypass

Run 38187893539 reviewed PR #6784 in full but posted nothing: the model's
Write to /tmp/review.md was refused. Claude Code checks file permissions
against Edit(path) rules only and never consults a Write(path) rule, so the
allow rule was dead - and so were the Write(//home/**) and Write(//opt/**)
deny rules meant to keep $GITHUB_ENV out of reach. All three are Edit rules
now; a local headless run confirms Write(path) is refused, Edit(path) lets
the file through, and an Edit deny still blocks a Write under it.

allowed_non_write_users="*" skipped the action's write-permission check for
every actor and warned each run. Only a pull_request_target from a fork
needs it; an `@claude review` requester is already OWNER/MEMBER/COLLABORATOR
and passes the action's own check.
MHSanaei 16 hours ago
parent
commit
5939fda397
1 changed files with 7 additions and 5 deletions
  1. 7 5
      .github/workflows/claude-pr-review.yml

+ 7 - 5
.github/workflows/claude-pr-review.yml

@@ -141,18 +141,20 @@ jobs:
         with:
         with:
           github_token: ${{ secrets.GITHUB_TOKEN }}
           github_token: ${{ secrets.GITHUB_TOKEN }}
           claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
           claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
-          allowed_non_write_users: "*"
+          # Only a fork author lacks write access; an `@claude review` requester passes
+          # the action's own check, so the bypass stays off for it.
+          allowed_non_write_users: ${{ github.event_name == 'pull_request_target' && '*' || '' }}
           # Claude Code loads a CLAUDE.md or .claude/rules/ file the moment a file
           # Claude Code loads a CLAUDE.md or .claude/rules/ file the moment a file
           # beside it is read, so a fork's copy under pr-head/ would brief its own review.
           # beside it is read, so a fork's copy under pr-head/ would brief its own review.
           settings: '{"claudeMdExcludes": ["**/pr-head/**"]}'
           settings: '{"claudeMdExcludes": ["**/pr-head/**"]}'
-          # allowedTools only pre-approves; it denies nothing. Only the deny list
-          # stops the review executing what it just checked out, or delegating.
+          # File rules must name Edit: Claude Code never consults a Write(path) rule,
+          # so Write(//tmp/review.md) left the review unable to save its body.
           claude_args: |
           claude_args: |
             --model claude-opus-5-5
             --model claude-opus-5-5
             --effort high
             --effort high
             --max-turns 300
             --max-turns 300
-            --allowedTools "Bash(gh api:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh search:*),Bash(gh pr diff:*),Bash(grep:*),Bash(rg:*),Bash(ls:*),Bash(find:*),Bash(sed:*),Bash(git log:*),Bash(git show:*),Bash(git diff:*),Bash(git blame:*),Bash(go doc:*),Bash(go env:*),Read,Glob,Grep,WebFetch,WebSearch,Write(//tmp/review.md)"
-            --disallowedTools "Agent,Bash(go build:*),Bash(go run:*),Bash(go test:*),Bash(go generate:*),Bash(go install:*),Bash(make:*),Bash(npm:*),Bash(npx:*),Bash(pnpm:*),Bash(yarn:*),Bash(node:*),Bash(bash:*),Bash(sh:*),Bash(docker:*),Bash(chmod:*),Edit,NotebookEdit,Write(//home/**),Write(//opt/**)"
+            --allowedTools "Bash(gh api:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh search:*),Bash(gh pr diff:*),Bash(grep:*),Bash(rg:*),Bash(ls:*),Bash(find:*),Bash(sed:*),Bash(git log:*),Bash(git show:*),Bash(git diff:*),Bash(git blame:*),Bash(go doc:*),Bash(go env:*),Read,Glob,Grep,WebFetch,WebSearch,Edit(//tmp/review.md)"
+            --disallowedTools "Agent,Bash(go build:*),Bash(go run:*),Bash(go test:*),Bash(go generate:*),Bash(go install:*),Bash(make:*),Bash(npm:*),Bash(npx:*),Bash(pnpm:*),Bash(yarn:*),Bash(node:*),Bash(bash:*),Bash(sh:*),Bash(docker:*),Bash(chmod:*),Edit,NotebookEdit,Edit(//home/**),Edit(//opt/**)"
           prompt: |
           prompt: |
             You are a Senior Software Engineer performing a production-grade code
             You are a Senior Software Engineer performing a production-grade code
             review of pull request #${{ env.PR }} in ${{ env.REPO }}. You are the
             review of pull request #${{ env.PR }} in ${{ env.REPO }}. You are the