|
| 1 | +name: Claude Code Review |
| 2 | + |
| 3 | +on: |
| 4 | + # Automatic review only on PR opened (no synchronize — author drives re-review) |
| 5 | + # Using pull_request_target because shellhub is public (fork PRs need secrets) |
| 6 | + pull_request_target: |
| 7 | + types: [opened] |
| 8 | + branches: [master] |
| 9 | + |
| 10 | + # Manual review via /review command in comments |
| 11 | + issue_comment: |
| 12 | + types: [created] |
| 13 | + pull_request_review_comment: |
| 14 | + types: [created] |
| 15 | + pull_request_review: |
| 16 | + types: [submitted] |
| 17 | + |
| 18 | +concurrency: |
| 19 | + group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} |
| 20 | + cancel-in-progress: true |
| 21 | + |
| 22 | +jobs: |
| 23 | + review: |
| 24 | + name: Claude Code Review |
| 25 | + if: | |
| 26 | + (github.event_name == 'pull_request_target' && !github.event.pull_request.draft) || |
| 27 | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/review')) || |
| 28 | + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/review')) || |
| 29 | + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/review')) |
| 30 | +
|
| 31 | + runs-on: ubuntu-latest |
| 32 | + permissions: |
| 33 | + contents: read |
| 34 | + pull-requests: write |
| 35 | + issues: write |
| 36 | + |
| 37 | + steps: |
| 38 | + # Checkout shellhub at workspace root (claude-code-action needs .git here) |
| 39 | + - name: Checkout shellhub (PR branch) |
| 40 | + uses: actions/checkout@v6 |
| 41 | + with: |
| 42 | + ref: ${{ github.event.pull_request.head.sha }} |
| 43 | + |
| 44 | + - name: Generate cross-repo token |
| 45 | + id: app-token |
| 46 | + uses: actions/create-github-app-token@v1 |
| 47 | + with: |
| 48 | + app-id: ${{ secrets.CLOUD_DISPATCH_APP_ID }} |
| 49 | + private-key: ${{ secrets.CLOUD_DISPATCH_APP_PRIVATE_KEY }} |
| 50 | + owner: shellhub-io |
| 51 | + repositories: cloud,claude |
| 52 | + |
| 53 | + - name: Determine cloud branch |
| 54 | + id: cloud-branch |
| 55 | + run: | |
| 56 | + BRANCH="${{ github.head_ref || github.ref_name }}" |
| 57 | + if git ls-remote --exit-code --heads \ |
| 58 | + https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/shellhub-io/cloud.git \ |
| 59 | + "$BRANCH" > /dev/null 2>&1; then |
| 60 | + echo "ref=$BRANCH" >> "$GITHUB_OUTPUT" |
| 61 | + else |
| 62 | + echo "ref=master" >> "$GITHUB_OUTPUT" |
| 63 | + fi |
| 64 | +
|
| 65 | + - name: Checkout cloud (context) |
| 66 | + uses: actions/checkout@v6 |
| 67 | + with: |
| 68 | + repository: shellhub-io/cloud |
| 69 | + token: ${{ steps.app-token.outputs.token }} |
| 70 | + ref: ${{ steps.cloud-branch.outputs.ref }} |
| 71 | + fetch-depth: 1 |
| 72 | + path: cloud |
| 73 | + |
| 74 | + - name: Checkout claude config |
| 75 | + uses: actions/checkout@v6 |
| 76 | + with: |
| 77 | + repository: shellhub-io/claude |
| 78 | + token: ${{ steps.app-token.outputs.token }} |
| 79 | + fetch-depth: 1 |
| 80 | + path: claude |
| 81 | + |
| 82 | + - name: Setup workspace context |
| 83 | + run: | |
| 84 | + # Link CLAUDE.md and .claude/ from claude repo into shellhub checkout |
| 85 | + ln -sf "$GITHUB_WORKSPACE/claude/.claude" "$GITHUB_WORKSPACE/.claude" |
| 86 | + ln -sf "$GITHUB_WORKSPACE/claude/shellhub/CLAUDE.md" "$GITHUB_WORKSPACE/CLAUDE.md" |
| 87 | +
|
| 88 | + - name: Run Claude Code Review |
| 89 | + uses: anthropics/claude-code-action@v1 |
| 90 | + with: |
| 91 | + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} |
| 92 | + github_token: ${{ secrets.GITHUB_TOKEN }} |
| 93 | + |
| 94 | + settings: | |
| 95 | + { |
| 96 | + "env": { |
| 97 | + "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" |
| 98 | + } |
| 99 | + } |
| 100 | + prompt: | |
| 101 | + You are the lead reviewer for PR #${{ github.event.pull_request.number || github.event.issue.number }} |
| 102 | + in shellhub-io/shellhub (Community Edition). |
| 103 | +
|
| 104 | + The cloud/ (enterprise) repo is checked out at $GITHUB_WORKSPACE/cloud/ for context. |
| 105 | + Focus the review on the shellhub diff. Use cloud/ only to understand cross-repo impact. |
| 106 | +
|
| 107 | + ## Step 1: Gather context |
| 108 | +
|
| 109 | + 1. Get the PR diff: |
| 110 | + gh pr diff ${{ github.event.pull_request.number || github.event.issue.number }} |
| 111 | + 2. Get the list of changed files: |
| 112 | + gh pr view ${{ github.event.pull_request.number || github.event.issue.number }} --json files |
| 113 | + 3. Fetch ALL existing review comments to avoid duplicating feedback: |
| 114 | + gh api repos/shellhub-io/shellhub/pulls/${{ github.event.pull_request.number || github.event.issue.number }}/comments |
| 115 | + gh api repos/shellhub-io/shellhub/pulls/${{ github.event.pull_request.number || github.event.issue.number }}/reviews |
| 116 | +
|
| 117 | + ## Step 2: Spawn 5 reviewer agents IN PARALLEL |
| 118 | +
|
| 119 | + Launch exactly 5 Task agents in a SINGLE message (so they run in parallel). |
| 120 | + Each agent MUST use model: "opus". Each agent gets: |
| 121 | + - The PR diff (pass it in the prompt) |
| 122 | + - The list of changed files |
| 123 | + - Read access to the full codebase (current directory for shellhub, $GITHUB_WORKSPACE/cloud/ for cloud) |
| 124 | +
|
| 125 | + Agent assignments: |
| 126 | + 1. **Code Quality**: Project conventions (CLAUDE.md), dead code, single responsibility, error handling, commit hygiene |
| 127 | + 2. **Security**: OWASP Top 10 (injection, XSS, CSRF, SSRF), hardcoded secrets, crypto/rand usage, access control, input validation |
| 128 | + 3. **Testing**: Missing tests for new/changed behavior, edge cases, test determinism, untested error paths |
| 129 | + 4. **Go/TypeScript Patterns**: Error wrapping (%w), goroutine leaks, context propagation, minimal interfaces, React patterns (no `any`, no unnecessary re-renders, Zustand) |
| 130 | + 5. **Architecture & Cross-repo**: If PR changes pkg/, check $GITHUB_WORKSPACE/cloud/ for impact. API contract changes, interface compatibility, breaking changes. |
| 131 | +
|
| 132 | + Each agent must return findings as a structured list: |
| 133 | + - file_path: exact path relative to repo root |
| 134 | + - line_start and line_end: exact line numbers in the NEW file |
| 135 | + - severity: critical | high | medium | low |
| 136 | + - description: what's wrong and why |
| 137 | + - suggestion: corrected code (if applicable), or empty string |
| 138 | +
|
| 139 | + Agents must NOT post any GitHub comments. They only return findings. |
| 140 | +
|
| 141 | + ## Step 3: Aggregate and deduplicate |
| 142 | +
|
| 143 | + After all 5 agents complete, collect their findings. Then: |
| 144 | + 1. Remove duplicate findings (same file + same line range + same issue) |
| 145 | + 2. If the same pattern repeats across multiple locations, keep only the first occurrence and note "Same issue also at: file:line, file:line, ..." |
| 146 | + 3. Compare against the existing review comments fetched in Step 1. Skip any finding already reported in a previous review thread on the same file and line. |
| 147 | +
|
| 148 | + ## Step 4: Post inline comments |
| 149 | +
|
| 150 | + For each remaining finding, post an inline comment using |
| 151 | + mcp__github_inline_comment__create_inline_comment on the specific file and line. |
| 152 | +
|
| 153 | + When a fix is available, include a GitHub suggestion block: |
| 154 | + ```suggestion |
| 155 | + corrected code here |
| 156 | + ``` |
| 157 | +
|
| 158 | + Do NOT re-report issues that already exist in previous review threads. |
| 159 | +
|
| 160 | + ## Step 5: Post closing comment |
| 161 | +
|
| 162 | + After all inline comments are posted, post ONE comment on the PR using `gh pr comment`: |
| 163 | + - Briefly summarize what was reviewed and how many findings were posted |
| 164 | + - State: "I've provided feedback and suggestions as inline comments on the code. |
| 165 | + When you've addressed the feedback and believe the PR is ready for a new review, |
| 166 | + comment `/review` to request a new review iteration." |
| 167 | +
|
| 168 | + claude_args: | |
| 169 | + --max-turns 30 |
| 170 | + --model claude-opus-4-6 |
| 171 | + --allowedTools "mcp__github_inline_comment__create_inline_comment" |
0 commit comments