From b07e3d1b399108ddd63d8b17e7bc8d59c4e1db74 Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Fri, 20 Mar 2026 17:27:15 -0700 Subject: [PATCH 1/2] feat(blocking-review): add cost guards and scope constraints Add max_turns (default 6), timeout_minutes (default 4), and model inputs to prevent runaway API usage. A review on transmission-filebot PR #23 consumed 20 turns, $1.00, and 11 minutes for a 109-line diff. - Add --max-turns and --model to claude_args - Add timeout-minutes to the Claude step - Add input validation with env vars (prevents expression injection) - Validate integer format before range checks - Add scope constraints to prompt (diff-only, no codebase exploration) - Replace plugin-based example template with blocking review caller Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/claude-blocking-review.yml | 55 ++++++++++++++++++- .github/workflows/claude-code-review.yml | 58 ++++++++------------ 2 files changed, 76 insertions(+), 37 deletions(-) diff --git a/.github/workflows/claude-blocking-review.yml b/.github/workflows/claude-blocking-review.yml index 849dfd0..f24d620 100644 --- a/.github/workflows/claude-blocking-review.yml +++ b/.github/workflows/claude-blocking-review.yml @@ -33,6 +33,21 @@ on: type: string required: false default: '' + max_turns: + description: 'Maximum Claude API turns (prevents runaway exploration). Default: 6.' + type: number + required: false + default: 6 + timeout_minutes: + description: 'Hard timeout for the Claude review step in minutes. Default: 4.' + type: number + required: false + default: 4 + model: + description: 'Claude model ID. Default: claude-sonnet-4-6.' + type: string + required: false + default: 'claude-sonnet-4-6' secrets: claude_oauth_token: description: 'Claude Code OAuth token (CLAUDE_CODE_OAUTH_TOKEN secret)' @@ -53,8 +68,39 @@ jobs: with: fetch-depth: 1 + - name: Validate inputs + env: + MODEL: ${{ inputs.model }} + MAX_TURNS: ${{ inputs.max_turns }} + TIMEOUT: ${{ inputs.timeout_minutes }} + run: | + # Validate model: alphanumeric, dots, hyphens only + if ! echo "$MODEL" | grep -qE '^[a-zA-Z0-9._-]+$'; then + echo "::error::Invalid model name. Must match [a-zA-Z0-9._-]+" + exit 1 + fi + # Validate max_turns: positive integer, capped at 20 + if ! echo "$MAX_TURNS" | grep -qE '^[0-9]+$'; then + echo "::error::max_turns must be a positive integer" + exit 1 + fi + if [ "$MAX_TURNS" -lt 1 ] || [ "$MAX_TURNS" -gt 20 ]; then + echo "::error::max_turns must be between 1 and 20" + exit 1 + fi + # Validate timeout_minutes: between 1 and 15 + if ! echo "$TIMEOUT" | grep -qE '^[0-9]+$'; then + echo "::error::timeout_minutes must be a positive integer" + exit 1 + fi + if [ "$TIMEOUT" -lt 1 ] || [ "$TIMEOUT" -gt 15 ]; then + echo "::error::timeout_minutes must be between 1 and 15" + exit 1 + fi + - name: Run Claude Code Review id: claude-review + timeout-minutes: ${{ inputs.timeout_minutes }} continue-on-error: true # infrastructure failure must not block merges uses: anthropics/claude-code-action@v1 with: @@ -63,6 +109,13 @@ jobs: REPO: ${{ github.repository }} PR NUMBER: ${{ inputs.pr_number }} + SCOPE CONSTRAINTS — follow these strictly: + - Read the PR diff using `gh pr diff` + - Read changed files for immediate context around modified lines + - Do NOT explore the broader codebase, run tests, or investigate unrelated files + - Focus your review on the diff — do not review unchanged code + - Complete your review in as few steps as possible + Please review this pull request and provide feedback on: - Code quality and best practices - Potential bugs or logic errors @@ -145,7 +198,7 @@ jobs: The verdict file (Step 4) is the primary signal read by CI. The verdict line appended to the comment (Step 2) is the fallback. Both must match. - 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 *)"' + 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 *)"' - name: Check review verdict if: always() diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b5e8cfd..b636752 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -1,44 +1,30 @@ name: Claude Code Review +# Example caller workflow for claude-blocking-review.yml. +# Copy this file into your repository's .github/workflows/ directory, +# configure the secret, and update the org/owner below if needed. +# +# NOTE: The 'uses:' reference below points to smartwatermelon/github-workflows +# which is the canonical source. Update the org name if you forked this repo. +# +# Prerequisites: +# 1. CLAUDE_CODE_OAUTH_TOKEN secret available (repo or org level) +# 2. (Optional) Branch protection rule requiring status check: +# "Claude Code Review / claude-review / run-review" + on: pull_request: types: [opened, synchronize, ready_for_review, reopened] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" jobs: claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' - plugins: 'code-review@claude-code-plugins' - prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - + uses: smartwatermelon/github-workflows/.github/workflows/claude-blocking-review.yml@v1 + with: + pr_number: ${{ github.event.pull_request.number }} + # extra_instructions: | + # Repo-specific guidance here. + # max_turns: 6 # default: 6 + # timeout_minutes: 4 # default: 4 + # model: claude-sonnet-4-6 # default: claude-sonnet-4-6 + secrets: + claude_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} From a22b4346cb608e9699cc873dbe70e817c633d616 Mon Sep 17 00:00:00 2001 From: Andrew Rich Date: Fri, 20 Mar 2026 17:41:47 -0700 Subject: [PATCH 2/2] fix: add permissions block to caller templateCodeQL flagged missing permissions on the example caller workflow. Add explicit permissions block matching what the called reusable workflow needs (contents:read, pull-requests:write, issues:write, id-token:write for claude-code-action OIDC auth). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/claude-code-review.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b636752..821dd10 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -12,6 +12,12 @@ name: Claude Code Review # 2. (Optional) Branch protection rule requiring status check: # "Claude Code Review / claude-review / run-review" +permissions: + contents: read + pull-requests: write + issues: write + id-token: write + on: pull_request: types: [opened, synchronize, ready_for_review, reopened]