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..821dd10 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -1,44 +1,36 @@ 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" + +permissions: + contents: read + pull-requests: write + issues: write + id-token: write + 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 }}