3333 type : string
3434 required : false
3535 default : ' '
36+ max_turns :
37+ description : ' Maximum Claude API turns (prevents runaway exploration). Default: 6.'
38+ type : number
39+ required : false
40+ default : 6
41+ timeout_minutes :
42+ description : ' Hard timeout for the Claude review step in minutes. Default: 4.'
43+ type : number
44+ required : false
45+ default : 4
46+ model :
47+ description : ' Claude model ID. Default: claude-sonnet-4-6.'
48+ type : string
49+ required : false
50+ default : ' claude-sonnet-4-6'
3651 secrets :
3752 claude_oauth_token :
3853 description : ' Claude Code OAuth token (CLAUDE_CODE_OAUTH_TOKEN secret)'
5368 with :
5469 fetch-depth : 1
5570
71+ - name : Validate inputs
72+ env :
73+ MODEL : ${{ inputs.model }}
74+ MAX_TURNS : ${{ inputs.max_turns }}
75+ TIMEOUT : ${{ inputs.timeout_minutes }}
76+ run : |
77+ # Validate model: alphanumeric, dots, hyphens only
78+ if ! echo "$MODEL" | grep -qE '^[a-zA-Z0-9._-]+$'; then
79+ echo "::error::Invalid model name. Must match [a-zA-Z0-9._-]+"
80+ exit 1
81+ fi
82+ # Validate max_turns: positive integer, capped at 20
83+ if ! echo "$MAX_TURNS" | grep -qE '^[0-9]+$'; then
84+ echo "::error::max_turns must be a positive integer"
85+ exit 1
86+ fi
87+ if [ "$MAX_TURNS" -lt 1 ] || [ "$MAX_TURNS" -gt 20 ]; then
88+ echo "::error::max_turns must be between 1 and 20"
89+ exit 1
90+ fi
91+ # Validate timeout_minutes: between 1 and 15
92+ if ! echo "$TIMEOUT" | grep -qE '^[0-9]+$'; then
93+ echo "::error::timeout_minutes must be a positive integer"
94+ exit 1
95+ fi
96+ if [ "$TIMEOUT" -lt 1 ] || [ "$TIMEOUT" -gt 15 ]; then
97+ echo "::error::timeout_minutes must be between 1 and 15"
98+ exit 1
99+ fi
100+
56101 - name : Run Claude Code Review
57102 id : claude-review
103+ timeout-minutes : ${{ inputs.timeout_minutes }}
58104 continue-on-error : true # infrastructure failure must not block merges
59105 uses : anthropics/claude-code-action@v1
60106 with :
@@ -63,6 +109,13 @@ jobs:
63109 REPO: ${{ github.repository }}
64110 PR NUMBER: ${{ inputs.pr_number }}
65111
112+ SCOPE CONSTRAINTS — follow these strictly:
113+ - Read the PR diff using `gh pr diff`
114+ - Read changed files for immediate context around modified lines
115+ - Do NOT explore the broader codebase, run tests, or investigate unrelated files
116+ - Focus your review on the diff — do not review unchanged code
117+ - Complete your review in as few steps as possible
118+
66119 Please review this pull request and provide feedback on:
67120 - Code quality and best practices
68121 - Potential bugs or logic errors
@@ -145,7 +198,7 @@ jobs:
145198 The verdict file (Step 4) is the primary signal read by CI. The verdict line
146199 appended to the comment (Step 2) is the fallback. Both must match.
147200
148- claude_args : ' --allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(echo *),Bash(cat *),Bash(tee *)"'
201+ claude_args : ' --max-turns ${{ inputs.max_turns }} --model ${{ inputs.model }} -- allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(echo *),Bash(cat *),Bash(tee *)"'
149202
150203 - name : Check review verdict
151204 if : always()
0 commit comments