From 60f134d778054a8c3606d164a92cad8f2a6ea17c Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 11:34:39 +0100 Subject: [PATCH 01/15] Add Claude Code Review Workflow - Introduced a new GitHub Actions workflow for automated code reviews using Claude. - The workflow triggers on pull requests, issue comments, and manual dispatch, allowing for flexible review processes. - Implemented steps for onboarding notifications, Claude API key detection, and PR context resolution. - Added functionality to post comments and reviews based on Claude's analysis, including inline comments for code issues. - Enhanced reporting with execution logs and summaries of the review process. This integration aims to streamline code review and improve code quality through automated analysis. --- .github/workflows/claude-review.yml | 542 ++++++++++++++++++++++++++++ 1 file changed, 542 insertions(+) create mode 100644 .github/workflows/claude-review.yml diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 0000000..e144086 --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,542 @@ +name: Claude Code Review + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to analyze (optional, for manual runs)' + required: false + type: string + issue_comment: + types: + - created + pull_request_review_comment: + types: + - created +concurrency: + group: claude-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + actions: read + +jobs: + claude-review: + name: Claude Code Analysis + if: >- + ( + github.event_name == 'pull_request' + ) || ( + github.event_name == 'issue_comment' && + github.event.comment && + contains(github.event.comment.body, '@claude') + ) || ( + github.event_name == 'pull_request_review_comment' && + github.event.comment && + contains(github.event.comment.body, '@claude') + ) || ( + github.event_name == 'workflow_dispatch' + ) + runs-on: self-hosted + steps: + - name: Post Claude onboarding note + if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const bodyLines = [ + '', + '', + '> [!IMPORTANT]', + '> ## Claude Review skipped', + '>', + '> Auto reviews are disabled on this repository.', + '>', + '> Please check the settings in the Claude workflow or the `.github/workflows/claude-review.yml` file in this repository. To trigger a single review, invoke the `@claude review` command.', + '>', + '', + '' + ]; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: bodyLines.join('\n') + }); + + - name: Determine Claude trigger + id: trigger + run: | + should_run=false + + case "$EVENT_NAME" in + issue_comment) + # Check if comment contains @claude review + if printf '%s' "$COMMENT_BODY" | grep -iqE '@claude\s+review\b'; then + # Verify this is actually a PR comment by checking if pull_request object exists + if [ "$HAS_PR_OBJECT" = "true" ]; then + echo "✓ Detected @claude review command in PR comment" + should_run=true + else + echo "✗ Comment is on an issue, not a PR. Skipping." + fi + else + echo "✗ Comment does not contain '@claude review'" + fi + ;; + pull_request_review_comment) + # For PR review comments, always check for @claude review + if printf '%s' "$COMMENT_BODY" | grep -iqE '@claude\s+review\b'; then + echo "✓ Detected @claude review command in PR review comment" + should_run=true + else + echo "✗ PR review comment does not contain '@claude review'" + fi + ;; + workflow_dispatch) + echo "✓ Manual workflow dispatch triggered" + should_run=true + ;; + *) + echo "✗ Event type '$EVENT_NAME' does not require trigger check" + ;; + esac + + echo "should_run=$should_run" >> "$GITHUB_OUTPUT" + echo "Final decision: should_run=$should_run" + env: + EVENT_NAME: ${{ github.event_name }} + COMMENT_BODY: ${{ github.event.comment.body || '' }} + HAS_PR_OBJECT: ${{ github.event.issue.pull_request != null || github.event.pull_request != null }} + + - name: Detect Claude API key + if: ${{ steps.trigger.outputs.should_run == 'true' }} + id: claude_token + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + run: | + if [ -z "${ANTHROPIC_API_KEY}" ]; then + echo "ANTHROPIC_API_KEY not configured, skipping Claude analysis."; + echo "available=false" >> "$GITHUB_OUTPUT" + else + echo "available=true" >> "$GITHUB_OUTPUT" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + fi + + - name: Resolve PR context + if: ${{ steps.trigger.outputs.should_run == 'true' }} + id: pr_context + uses: actions/github-script@v7 + env: + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + ISSUE_NUMBER: ${{ github.event.issue.number || '' }} + PULL_URL: ${{ github.event.issue.pull_request.url || '' }} + COMMENT_PR_URL: ${{ github.event.comment.pull_request_url || '' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const parseNumberFromUrl = (url) => { + if (!url || typeof url !== 'string') { + return 0; + } + const segments = url.trim().split('/').filter(Boolean); + const last = segments[segments.length - 1]; + const value = Number(last); + return Number.isFinite(value) ? value : 0; + }; + + let prNumber = Number(process.env.PR_NUMBER || 0); + const issueNumberEnv = Number(process.env.ISSUE_NUMBER || 0); + + if (!prNumber && context.payload.pull_request?.number) { + prNumber = context.payload.pull_request.number; + } + + if (!prNumber && context.payload.issue?.number) { + prNumber = context.payload.issue.number; + } + + if (!prNumber && issueNumberEnv) { + prNumber = issueNumberEnv; + } + + if (!prNumber && context.payload.issue?.pull_request?.url) { + prNumber = parseNumberFromUrl(context.payload.issue.pull_request.url); + } + + if (!prNumber && process.env.PULL_URL) { + prNumber = parseNumberFromUrl(process.env.PULL_URL); + } + + if (!prNumber && process.env.COMMENT_PR_URL) { + prNumber = parseNumberFromUrl(process.env.COMMENT_PR_URL); + } + + if (!prNumber) { + core.info('PR number not resolved; proceeding without PR context.'); + core.setOutput('pr_number', ''); + core.setOutput('head_sha', ''); + core.setOutput('head_ref', ''); + core.setOutput('checkout_ref', ''); + core.setOutput('head_repo', ''); + return; + } + + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + const headSha = pr.head.sha || ''; + const headRef = pr.head.ref || ''; + const checkoutRef = headSha || (headRef ? `refs/heads/${headRef}` : `refs/pull/${prNumber}/head`); + const headRepo = pr.head.repo?.full_name || `${context.repo.owner}/${context.repo.repo}`; + + core.setOutput('pr_number', pr.number.toString()); + core.setOutput('head_sha', headSha); + core.setOutput('head_ref', headRef); + core.setOutput('checkout_ref', checkoutRef); + core.setOutput('head_repo', headRepo); + + - name: Notify missing Anthropic key + if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'false' }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const marker = ''; + const docUrl = 'https://wiki.gluzdov.com/doc/claude-review-workflow-setup-Dbg0WdgMsk'; + const prNumber = Number('${{ steps.pr_context.outputs.pr_number || '' }}'); + if (!prNumber) { + core.info('PR number unavailable; skipping reminder comment.'); + return; + } + + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100 + }); + const existing = comments.find(comment => comment.body && comment.body.includes(marker)); + if (existing) { + core.info('Setup reminder already posted.'); + return; + } + + const body = `${marker} + + ### Claude Review Setup Required + + The Claude review workflow is disabled because \`ANTHROPIC_API_KEY\` is not configured. + + Please follow the setup guide: ${docUrl} + `; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body + }); + + + - name: Checkout repository + if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' }} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + repository: ${{ steps.pr_context.outputs.head_repo || github.repository }} + ref: ${{ steps.pr_context.outputs.checkout_ref || github.event.pull_request.head.sha || github.event.pull_request.head.ref || github.sha }} + + - name: Run Claude Code Action v3.5 Sonnet + if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' }} + id: claude_review + continue-on-error: true + uses: anthropics/claude-code-base-action@beta + env: + ANTHROPIC_MODEL: claude-3-5-sonnet-20240620 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + allowed_tools: | + Bash(git diff --name-only HEAD~1) + Bash(git diff HEAD~1) + View + Glob + Grep + Read + claude_env: | + - DEBUG: true + - LOG_LEVEL: debug + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ steps.pr_context.outputs.pr_number || github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + + Review only the changed files and diff hunks in this pull request. Focus on defects, security/performance risks, missing tests, or violations of project conventions. + + Respond STRICTLY with valid JSON matching this schema (no extra text): + { + "summary": "Concise markdown summary (optional)", + "inline_comments": [ + { + "path": "relative/path/from/repo/root.ext", + "line": , + "severity": "BUG|SECURITY|PERFORMANCE|SUGGESTION", + "category": "blocking_operation|missing_origin_check|etc", + "rule_id": "RULE_ID", + "summary": "Brief description", + "body": "**[EMOJI] [SEVERITY]: [Brief description]**\n\n[Details]\n\n**Suggestion:**\n```rust\n[fix]\n```\n\n**Why this matters:** [Impact]", + "confidence": 0.9 + } + ] + } + + Rules: + - Include an inline comment only when it points to a concrete issue in the diff. Skip praise. + - If you found nothing important, return {"summary": "", "inline_comments": []}. + - For each inline comment, reference the exact changed line so the position is unambiguous. + + max_turns: 25 + + - name: Process Claude review + if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' && steps.claude_review.outcome == 'success' }} + uses: actions/github-script@v7 + env: + EXECUTION_FILE: ${{ steps.claude_review.outputs.execution_file }} + PR_NUMBER: ${{ steps.pr_context.outputs.pr_number || github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + HEAD_SHA: ${{ steps.pr_context.outputs.head_sha || github.event.pull_request.head.sha || github.sha }} + OUTPUT_DIR: pr-review-results + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const path = require('path'); + + const executionFile = process.env.EXECUTION_FILE; + if (!executionFile || !fs.existsSync(executionFile)) { + core.warning('Claude execution file not found; skipping processing.'); + return; + } + + const prNumber = Number(process.env.PR_NUMBER || 0); + if (!prNumber) { + core.warning('PR number unavailable; cannot post review.'); + return; + } + + const executionLog = JSON.parse(fs.readFileSync(executionFile, 'utf8')); + + const COMMENT_PREFIX = 'Claude Claude\n\n'; + let totalCost = null; + const considerCost = (candidate) => { + if (!candidate || typeof candidate !== 'object') { + return; + } + if (typeof candidate.total_cost_usd === 'number') { + totalCost = candidate.total_cost_usd; + } else if (candidate.result && typeof candidate.result.total_cost_usd === 'number') { + totalCost = candidate.result.total_cost_usd; + } + }; + + const visit = (value) => { + if (Array.isArray(value)) { + value.forEach(visit); + } else if (value && typeof value === 'object') { + considerCost(value); + visit(value.message); + visit(value.data); + } + }; + visit(executionLog); + + const entries = Array.isArray(executionLog) ? executionLog : [executionLog]; + const assistantEntry = [...entries].reverse().find((entry) => entry && entry.type === 'assistant' && Array.isArray(entry.message?.content) && entry.message.content.length); + if (!assistantEntry) { + core.info('No assistant response found to extract review from.'); + return; + } + + const textPayload = assistantEntry.message.content + .filter((part) => part && part.type === 'text' && typeof part.text === 'string') + .map((part) => part.text) + .join('\n') + .trim(); + + if (!textPayload) { + core.info('Assistant response did not include textual content to parse.'); + return; + } + + const tryParseJson = (input) => { + try { + return JSON.parse(input); + } catch (error) { + return null; + } + }; + + let reviewPayload = tryParseJson(textPayload); + if (!reviewPayload) { + const jsonBlockMatch = textPayload.match(/```json\s*([\s\S]*?)```/i) || textPayload.match(/```\s*([\s\S]*?)```/); + if (jsonBlockMatch && jsonBlockMatch[1]) { + reviewPayload = tryParseJson(jsonBlockMatch[1].trim()); + } + } + if (!reviewPayload) { + const firstBrace = textPayload.indexOf('{'); + const lastBrace = textPayload.lastIndexOf('}'); + if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { + reviewPayload = tryParseJson(textPayload.slice(firstBrace, lastBrace + 1)); + } + } + + if (!reviewPayload || typeof reviewPayload !== 'object') { + core.warning('Claude response did not include a parsable JSON review payload.'); + return; + } + + const summary = typeof reviewPayload.summary === 'string' ? reviewPayload.summary.trim() : ''; + const inlineComments = Array.isArray(reviewPayload.inline_comments) ? reviewPayload.inline_comments : []; + + const comments = inlineComments + .map((comment) => { + if (!comment || typeof comment.path !== 'string' || typeof comment.line !== 'number' || !comment.body) { + return null; + } + const body = String(comment.body).trim(); + if (!body) { + return null; + } + return { + path: comment.path, + line: comment.line, + side: 'RIGHT', + body: `${COMMENT_PREFIX}${body}` + }; + }) + .filter(Boolean); + + if (comments.length === 0 && !summary && totalCost === null) { + core.info('Claude returned no actionable feedback.'); + return; + } + + const costLine = typeof totalCost === 'number' ? `Estimated cost: $${totalCost.toFixed(5)}` : ''; + let reviewBody = ''; + if (summary || costLine) { + const bodySegments = ['## Claude Code Review']; + if (summary) { + bodySegments.push('', summary); + } + if (costLine) { + bodySegments.push('', `*${costLine}*`); + } + reviewBody = `${COMMENT_PREFIX}${bodySegments.join('\n')}`; + } + + let headSha = process.env.HEAD_SHA || ''; + if (!headSha) { + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + headSha = pr.head.sha; + } + + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + commit_id: headSha, + event: 'COMMENT', + body: reviewBody, + comments + }); + + const resultsDir = process.env.OUTPUT_DIR || 'pr-review-results'; + fs.mkdirSync(resultsDir, { recursive: true }); + const resultPayload = { + service: 'claude', + status: 'success', + cost_usd: totalCost || 0, + stats: { + comments: comments.length, + summary: Boolean(summary) + }, + timestamp: new Date().toISOString() + }; + fs.writeFileSync(path.join(resultsDir, 'claude.json'), JSON.stringify(resultPayload, null, 2)); + + const summaryLines = ['### Claude Code Review', '', `- Comments posted: ${comments.length}`, `- Estimated cost: $${(totalCost || 0).toFixed(5)}`]; + if (summary) { + summaryLines.push('', summary); + } + fs.writeFileSync(path.join(resultsDir, 'claude-summary.md'), summaryLines.join('\n')); + + await core.summary + .addHeading('Claude Code Cost Summary') + .addList([ + 'Outcome: success', + `Estimated cost: $${(totalCost || 0).toFixed(5)}` + ]) + .write(); + + const marker = ''; + const stickyLines = [ + marker, + '### Claude Code Cost Summary', + '', + '- Outcome: success', + `- Estimated cost: $${(totalCost || 0).toFixed(5)}`, + '' + ]; + const stickyBody = `${COMMENT_PREFIX}${stickyLines.join('\n')}`; + + const existingComments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100 + }); + + const existing = [...existingComments].reverse().find((comment) => comment.body && comment.body.includes(marker)); + if (existing) { + if (existing.body.trim() !== stickyBody.trim()) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body: stickyBody + }); + } else { + core.info('Claude cost summary already up to date.'); + } + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: stickyBody + }); + } + + + - name: Upload Claude execution log + if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' && steps.claude_review.outputs.execution_file != '' }} + uses: actions/upload-artifact@v4 + with: + name: pr-review-claude-execution-${{ github.event.pull_request.number || inputs.pr_number || github.run_id }} + path: ${{ steps.claude_review.outputs.execution_file }} + retention-days: ${{ vars.PR_REVIEW_ARTIFACT_RETENTION_DAYS || 7 }} + if-no-files-found: ignore \ No newline at end of file From 9075dff3beb434806b8351a101f9d43ad751f747 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 11:51:50 +0100 Subject: [PATCH 02/15] Refactor Claude review workflow for improved efficiency - Removed unnecessary blank lines for cleaner code. - Reduced the maximum turns for Claude's review process from 25 to 5, optimizing performance. - Ensured consistent formatting in the workflow file. These changes enhance the readability and efficiency of the Claude review workflow. --- .github/workflows/claude-review.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index e144086..f2e1028 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -77,7 +77,7 @@ jobs: id: trigger run: | should_run=false - + case "$EVENT_NAME" in issue_comment) # Check if comment contains @claude review @@ -110,7 +110,7 @@ jobs: echo "✗ Event type '$EVENT_NAME' does not require trigger check" ;; esac - + echo "should_run=$should_run" >> "$GITHUB_OUTPUT" echo "Final decision: should_run=$should_run" env: @@ -249,7 +249,6 @@ jobs: body }); - - name: Checkout repository if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' }} uses: actions/checkout@v4 @@ -305,7 +304,7 @@ jobs: - If you found nothing important, return {"summary": "", "inline_comments": []}. - For each inline comment, reference the exact changed line so the position is unambiguous. - max_turns: 25 + max_turns: 5 - name: Process Claude review if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' && steps.claude_review.outcome == 'success' }} @@ -531,7 +530,6 @@ jobs: }); } - - name: Upload Claude execution log if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' && steps.claude_review.outputs.execution_file != '' }} uses: actions/upload-artifact@v4 @@ -539,4 +537,4 @@ jobs: name: pr-review-claude-execution-${{ github.event.pull_request.number || inputs.pr_number || github.run_id }} path: ${{ steps.claude_review.outputs.execution_file }} retention-days: ${{ vars.PR_REVIEW_ARTIFACT_RETENTION_DAYS || 7 }} - if-no-files-found: ignore \ No newline at end of file + if-no-files-found: ignore From 7afeea8f38aa99fc3035c498dbc86c924b2ef541 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 11:53:14 +0100 Subject: [PATCH 03/15] Update Claude review workflow to use the latest model version - Changed the ANTHROPIC_MODEL environment variable from a specific version to the latest version for improved performance and compatibility. This update ensures the workflow utilizes the most current capabilities of the Claude model. --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index f2e1028..d1a3fd1 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -263,7 +263,7 @@ jobs: continue-on-error: true uses: anthropics/claude-code-base-action@beta env: - ANTHROPIC_MODEL: claude-3-5-sonnet-20240620 + ANTHROPIC_MODEL: claude-3-5-sonnet-latest with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} allowed_tools: | From d0dcfbe8b8438c7a14f1502f2a183304a4c2c5db Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 11:55:59 +0100 Subject: [PATCH 04/15] Add environment variable for Claude review documentation URL - Introduced a new environment variable `CLAUDE_REVIEW_DOC_URL` to dynamically set the documentation URL for the Claude review setup. - Updated the workflow to utilize this variable, enhancing flexibility and maintainability of the documentation link. These changes improve the workflow's adaptability to changes in documentation location. --- .github/workflows/claude-review.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index d1a3fd1..d8f20be 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -28,6 +28,9 @@ permissions: pull-requests: write actions: read +env: + CLAUDE_REVIEW_DOC_URL: ${{ vars.CLAUDE_REVIEW_DOC_URL || format('https://github.com/%s/blob/main/README.md#claude-review-setup', github.repository) }} + jobs: claude-review: name: Claude Code Analysis @@ -215,7 +218,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const marker = ''; - const docUrl = 'https://wiki.gluzdov.com/doc/claude-review-workflow-setup-Dbg0WdgMsk'; + const docUrl = process.env.CLAUDE_REVIEW_DOC_URL || ''; const prNumber = Number('${{ steps.pr_context.outputs.pr_number || '' }}'); if (!prNumber) { core.info('PR number unavailable; skipping reminder comment.'); From b45f56aa5a280cdd8e861ded9b7177200ccedd22 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 12:01:38 +0100 Subject: [PATCH 05/15] Update Claude review workflow documentation URL - Removed the environment variable `CLAUDE_REVIEW_DOC_URL` and replaced it with a hardcoded URL for the Claude review setup documentation. - This change simplifies the workflow by providing a direct link to the documentation, ensuring users have immediate access to the necessary setup instructions. These modifications enhance the clarity and accessibility of the documentation for users of the Claude review workflow. --- .github/workflows/claude-review.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index d8f20be..592625c 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -28,9 +28,6 @@ permissions: pull-requests: write actions: read -env: - CLAUDE_REVIEW_DOC_URL: ${{ vars.CLAUDE_REVIEW_DOC_URL || format('https://github.com/%s/blob/main/README.md#claude-review-setup', github.repository) }} - jobs: claude-review: name: Claude Code Analysis @@ -218,7 +215,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const marker = ''; - const docUrl = process.env.CLAUDE_REVIEW_DOC_URL || ''; + const docUrl = process.env.CLAUDE_REVIEW_DOC_URL || 'https://wiki.gluzdov.com/doc/claude-review-workflow-setup-Dbg0WdgMsk'; const prNumber = Number('${{ steps.pr_context.outputs.pr_number || '' }}'); if (!prNumber) { core.info('PR number unavailable; skipping reminder comment.'); From 9ea853e1e80c19f46555f56913b12b8bfe4e91f2 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 12:14:36 +0100 Subject: [PATCH 06/15] Enhance Claude review workflow comments and onboarding process - Added a visual comment prefix featuring the Claude AI symbol to improve the onboarding experience. - Simplified the onboarding comment structure by removing unnecessary lines and clarifying instructions for triggering a Claude review. - Ensured consistent formatting and improved clarity in the setup reminder comment. These changes aim to enhance user experience and streamline the onboarding process for the Claude review workflow. --- .github/workflows/claude-review.yml | 45 +++++++++++++---------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 592625c..fd3a88b 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -53,19 +53,15 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | + const COMMENT_PREFIX = 'Claude Claude\n\n'; const bodyLines = [ - '', + `${COMMENT_PREFIX}`, + '', '', - '> [!IMPORTANT]', - '> ## Claude Review skipped', - '>', - '> Auto reviews are disabled on this repository.', - '>', - '> Please check the settings in the Claude workflow or the `.github/workflows/claude-review.yml` file in this repository. To trigger a single review, invoke the `@claude review` command.', - '>', + '> [!NOTE] To trigger a Claude review, invoke the `@claude review` command.', '', - '' - ]; + '' + ].join('\n'); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, @@ -216,6 +212,7 @@ jobs: script: | const marker = ''; const docUrl = process.env.CLAUDE_REVIEW_DOC_URL || 'https://wiki.gluzdov.com/doc/claude-review-workflow-setup-Dbg0WdgMsk'; + const COMMENT_PREFIX = 'Claude Claude\n\n'; const prNumber = Number('${{ steps.pr_context.outputs.pr_number || '' }}'); if (!prNumber) { core.info('PR number unavailable; skipping reminder comment.'); @@ -229,25 +226,23 @@ jobs: per_page: 100 }); const existing = comments.find(comment => comment.body && comment.body.includes(marker)); - if (existing) { - core.info('Setup reminder already posted.'); - return; - } - const body = `${marker} + if (!existing) { + const body = `${COMMENT_PREFIX}${marker} - ### Claude Review Setup Required + ### Claude Review Setup Required - The Claude review workflow is disabled because \`ANTHROPIC_API_KEY\` is not configured. + The Claude review workflow is disabled because \`ANTHROPIC_API_KEY\` is not configured. - Please follow the setup guide: ${docUrl} - `; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body - }); + Please follow the setup guide: ${docUrl} + `; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body + }); + } - name: Checkout repository if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' }} From 2ede253b0b80ac08b0b77423e74d9b2ab7c733a2 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 12:24:34 +0100 Subject: [PATCH 07/15] Refine Claude review workflow to restrict review requests to trusted users - Updated the workflow to ensure that only users with the association of OWNER, MEMBER, or COLLABORATOR can trigger a review request using the '@claude review' command in issue and PR comments. - Enhanced feedback messages to clarify when a comment is skipped due to author association or if the comment is not on a PR. These changes improve the security and integrity of the review process by limiting access to trusted contributors. --- .github/workflows/claude-review.yml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index fd3a88b..db3361c 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -76,24 +76,29 @@ jobs: case "$EVENT_NAME" in issue_comment) - # Check if comment contains @claude review if printf '%s' "$COMMENT_BODY" | grep -iqE '@claude\s+review\b'; then - # Verify this is actually a PR comment by checking if pull_request object exists - if [ "$HAS_PR_OBJECT" = "true" ]; then - echo "✓ Detected @claude review command in PR comment" - should_run=true + if [ "$AUTHOR_ASSOCIATION" = "OWNER" ] || [ "$AUTHOR_ASSOCIATION" = "MEMBER" ] || [ "$AUTHOR_ASSOCIATION" = "COLLABORATOR" ]; then + if [ "$HAS_PR_OBJECT" = "true" ]; then + echo "✓ Trusted user requested @claude review on PR comment" + should_run=true + else + echo "✗ Comment is on an issue, not a PR. Skipping." + fi else - echo "✗ Comment is on an issue, not a PR. Skipping." + echo "✗ Comment author association '$AUTHOR_ASSOCIATION' not permitted" fi else echo "✗ Comment does not contain '@claude review'" fi ;; pull_request_review_comment) - # For PR review comments, always check for @claude review if printf '%s' "$COMMENT_BODY" | grep -iqE '@claude\s+review\b'; then - echo "✓ Detected @claude review command in PR review comment" - should_run=true + if [ "$AUTHOR_ASSOCIATION" = "OWNER" ] || [ "$AUTHOR_ASSOCIATION" = "MEMBER" ] || [ "$AUTHOR_ASSOCIATION" = "COLLABORATOR" ]; then + echo "✓ Trusted user requested @claude review on PR review comment" + should_run=true + else + echo "✗ Review author association '$AUTHOR_ASSOCIATION' not permitted" + fi else echo "✗ PR review comment does not contain '@claude review'" fi @@ -113,6 +118,7 @@ jobs: EVENT_NAME: ${{ github.event_name }} COMMENT_BODY: ${{ github.event.comment.body || '' }} HAS_PR_OBJECT: ${{ github.event.issue.pull_request != null || github.event.pull_request != null }} + AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association || github.event.pull_request.user.type }} - name: Detect Claude API key if: ${{ steps.trigger.outputs.should_run == 'true' }} From 38a0cd6c94ea6b75eecba440d863d052cd02f8ff Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 12:44:38 +0100 Subject: [PATCH 08/15] Update Claude review workflow to use a specific version of the code base action - Changed the action reference from a beta version to a specific commit hash for the `claude-code-base-action`, ensuring stability and predictability in the workflow execution. This update enhances the reliability of the Claude review process by locking the action to a known state. --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index db3361c..71844be 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -262,7 +262,7 @@ jobs: if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' }} id: claude_review continue-on-error: true - uses: anthropics/claude-code-base-action@beta + uses: anthropics/claude-code-base-action@e8132bc5e637a42c27763fc757faa37e1ee43b34 env: ANTHROPIC_MODEL: claude-3-5-sonnet-latest with: From 0ff9e5aa5daf9b95cc9091b37aa76e6ac47f5501 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 12:58:47 +0100 Subject: [PATCH 09/15] Add pull request event handling to Claude review workflow - Enhanced the Claude review workflow by adding support for pull request events, allowing the workflow to trigger and provide feedback when a pull request is created or updated. - This addition improves the responsiveness of the review process, ensuring that pull requests are acknowledged and processed appropriately. These changes aim to streamline the review workflow and enhance user interaction during pull request events. --- .github/workflows/claude-review.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 71844be..227da06 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -103,6 +103,10 @@ jobs: echo "✗ PR review comment does not contain '@claude review'" fi ;; + pull_request) + echo "✓ Pull request event triggered" + should_run=true + ;; workflow_dispatch) echo "✓ Manual workflow dispatch triggered" should_run=true From a6a751339619347dc5add1b2ec5715b5fda72220 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 13:39:25 +0100 Subject: [PATCH 10/15] Enhance Claude review workflow with environment variables and error handling - Introduced environment variables for comment prefixes and markers to improve flexibility and maintainability in the Claude review workflow. - Updated the comment handling logic to ensure consistent formatting and improved clarity in onboarding and cost summary comments. - Added error handling for Docker Scout image scanning to prevent workflow failures. These changes aim to streamline the review process and enhance the overall robustness of the workflow. --- .github/workflows/claude-review.yml | 46 +++++++++++++++++------------ .github/workflows/code-quality.yml | 3 +- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 227da06..3c22961 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -46,6 +46,10 @@ jobs: github.event_name == 'workflow_dispatch' ) runs-on: self-hosted + env: + COMMENT_PREFIX: 'Claude Claude\n\n' + CLAUDE_COST_SUMMARY_MARKER: '' + CLAUDE_SETUP_REMINDER_MARKER: '' steps: - name: Post Claude onboarding note if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} @@ -53,10 +57,9 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const COMMENT_PREFIX = 'Claude Claude\n\n'; - const bodyLines = [ - `${COMMENT_PREFIX}`, - '', + const commentPrefix = process.env.COMMENT_PREFIX || ''; + const onboardingBody = [ + `${commentPrefix}`, '', '> [!NOTE] To trigger a Claude review, invoke the `@claude review` command.', '', @@ -66,7 +69,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - body: bodyLines.join('\n') + body: onboardingBody }); - name: Determine Claude trigger @@ -76,7 +79,7 @@ jobs: case "$EVENT_NAME" in issue_comment) - if printf '%s' "$COMMENT_BODY" | grep -iqE '@claude\s+review\b'; then + if printf '%s' "$COMMENT_BODY" | grep -qiE '@claude[[:space:]]+review'; then if [ "$AUTHOR_ASSOCIATION" = "OWNER" ] || [ "$AUTHOR_ASSOCIATION" = "MEMBER" ] || [ "$AUTHOR_ASSOCIATION" = "COLLABORATOR" ]; then if [ "$HAS_PR_OBJECT" = "true" ]; then echo "✓ Trusted user requested @claude review on PR comment" @@ -92,7 +95,7 @@ jobs: fi ;; pull_request_review_comment) - if printf '%s' "$COMMENT_BODY" | grep -iqE '@claude\s+review\b'; then + if printf '%s' "$COMMENT_BODY" | grep -qiE '@claude[[:space:]]+review'; then if [ "$AUTHOR_ASSOCIATION" = "OWNER" ] || [ "$AUTHOR_ASSOCIATION" = "MEMBER" ] || [ "$AUTHOR_ASSOCIATION" = "COLLABORATOR" ]; then echo "✓ Trusted user requested @claude review on PR review comment" should_run=true @@ -103,9 +106,13 @@ jobs: echo "✗ PR review comment does not contain '@claude review'" fi ;; - pull_request) - echo "✓ Pull request event triggered" - should_run=true + pull_request) + if [ "$IS_FORK" = "true" ]; then + echo "✗ Pull request originates from a fork; skipping self-hosted run" + else + echo "✓ Pull request event triggered" + should_run=true + fi ;; workflow_dispatch) echo "✓ Manual workflow dispatch triggered" @@ -122,7 +129,8 @@ jobs: EVENT_NAME: ${{ github.event_name }} COMMENT_BODY: ${{ github.event.comment.body || '' }} HAS_PR_OBJECT: ${{ github.event.issue.pull_request != null || github.event.pull_request != null }} - AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association || github.event.pull_request.user.type }} + AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association || (github.event.pull_request && github.event.pull_request.author_association) || 'UNKNOWN' }} + IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork && 'true' || 'false' }} - name: Detect Claude API key if: ${{ steps.trigger.outputs.should_run == 'true' }} @@ -220,9 +228,9 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const marker = ''; + const marker = process.env.CLAUDE_SETUP_REMINDER_MARKER || ''; const docUrl = process.env.CLAUDE_REVIEW_DOC_URL || 'https://wiki.gluzdov.com/doc/claude-review-workflow-setup-Dbg0WdgMsk'; - const COMMENT_PREFIX = 'Claude Claude\n\n'; + const commentPrefix = process.env.COMMENT_PREFIX || ''; const prNumber = Number('${{ steps.pr_context.outputs.pr_number || '' }}'); if (!prNumber) { core.info('PR number unavailable; skipping reminder comment.'); @@ -238,7 +246,7 @@ jobs: const existing = comments.find(comment => comment.body && comment.body.includes(marker)); if (!existing) { - const body = `${COMMENT_PREFIX}${marker} + const body = `${commentPrefix}${marker} ### Claude Review Setup Required @@ -339,7 +347,7 @@ jobs: const executionLog = JSON.parse(fs.readFileSync(executionFile, 'utf8')); - const COMMENT_PREFIX = 'Claude Claude\n\n'; + const commentPrefix = process.env.COMMENT_PREFIX || ''; let totalCost = null; const considerCost = (candidate) => { if (!candidate || typeof candidate !== 'object') { @@ -425,7 +433,7 @@ jobs: path: comment.path, line: comment.line, side: 'RIGHT', - body: `${COMMENT_PREFIX}${body}` + body: `${commentPrefix}${body}` }; }) .filter(Boolean); @@ -445,7 +453,7 @@ jobs: if (costLine) { bodySegments.push('', `*${costLine}*`); } - reviewBody = `${COMMENT_PREFIX}${bodySegments.join('\n')}`; + reviewBody = `${commentPrefix}${bodySegments.join('\n')}`; } let headSha = process.env.HEAD_SHA || ''; @@ -496,7 +504,7 @@ jobs: ]) .write(); - const marker = ''; + const marker = process.env.CLAUDE_COST_SUMMARY_MARKER || ''; const stickyLines = [ marker, '### Claude Code Cost Summary', @@ -505,7 +513,7 @@ jobs: `- Estimated cost: $${(totalCost || 0).toFixed(5)}`, '' ]; - const stickyBody = `${COMMENT_PREFIX}${stickyLines.join('\n')}`; + const stickyBody = `${commentPrefix}${stickyLines.join('\n')}`; const existingComments = await github.paginate(github.rest.issues.listComments, { owner: context.repo.owner, diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index a8aa1d4..2c39e24 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -26,7 +26,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Install dependencies run: cd workers/main && npm install - name: Run tests with coverage @@ -81,6 +81,7 @@ jobs: load: true - name: Scan ${{ matrix.service.name }} image uses: docker/scout-action@v1 + continue-on-error: true id: docker_scout with: command: cves From f2acde3a9cafe36e04780f705f8839dc47c1ad10 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 13:45:01 +0100 Subject: [PATCH 11/15] Fix author association logic in Claude review workflow - Updated the author association retrieval in the Claude review workflow to ensure it correctly checks the pull request's author association, improving the accuracy of user permissions during review requests. This change enhances the reliability of the review process by ensuring proper user validation. --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 3c22961..92c0dc5 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -129,7 +129,7 @@ jobs: EVENT_NAME: ${{ github.event_name }} COMMENT_BODY: ${{ github.event.comment.body || '' }} HAS_PR_OBJECT: ${{ github.event.issue.pull_request != null || github.event.pull_request != null }} - AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association || (github.event.pull_request && github.event.pull_request.author_association) || 'UNKNOWN' }} + AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association || github.event.pull_request.author_association || 'UNKNOWN' }} IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork && 'true' || 'false' }} - name: Detect Claude API key From e4bd91989f919efc8db619450c7de57dc802e8c2 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 13:45:53 +0100 Subject: [PATCH 12/15] Update comment prefix in Claude review workflow for improved formatting - Removed unnecessary newline from the COMMENT_PREFIX environment variable in the Claude review workflow, enhancing the clarity and consistency of comments generated during the review process. This change aims to streamline the output of the workflow and improve user experience. --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 92c0dc5..adb02ab 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -47,7 +47,7 @@ jobs: ) runs-on: self-hosted env: - COMMENT_PREFIX: 'Claude Claude\n\n' + COMMENT_PREFIX: 'Claude Claude' CLAUDE_COST_SUMMARY_MARKER: '' CLAUDE_SETUP_REMINDER_MARKER: '' steps: From dbf6d8399e1b5bfcbada4682ad0529d794fb8d79 Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 12 Nov 2025 14:09:40 +0100 Subject: [PATCH 13/15] Update concurrency group identifier in Claude review workflow - Modified the concurrency group identifier in the Claude review workflow to use `github.run_id` instead of the previous identifiers. This change enhances the uniqueness of the concurrency group, ensuring that concurrent runs are managed more effectively. This update aims to improve the workflow's handling of concurrent executions and prevent potential conflicts. --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index adb02ab..0b55ec4 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -20,7 +20,7 @@ on: types: - created concurrency: - group: claude-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number || github.ref }} + group: claude-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number || github.run_id }} cancel-in-progress: true permissions: From b8e391d166c21ba659dc5c4652519ff7fedeb7cc Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 19 Nov 2025 09:43:00 +0100 Subject: [PATCH 14/15] Remove continue-on-error option from Docker Scout action in code quality workflow - Eliminated the continue-on-error setting for the Docker Scout action to ensure that any errors during image scanning will halt the workflow, improving error visibility and response. This change aims to enhance the reliability of the code quality checks by ensuring that issues are addressed promptly. --- .github/workflows/code-quality.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 2c39e24..6a3e9fe 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -81,7 +81,6 @@ jobs: load: true - name: Scan ${{ matrix.service.name }} image uses: docker/scout-action@v1 - continue-on-error: true id: docker_scout with: command: cves From e759e60c7f344b5eb483db039300ff362ddca24a Mon Sep 17 00:00:00 2001 From: "anatoly.shipitz" Date: Wed, 19 Nov 2025 11:17:34 +0100 Subject: [PATCH 15/15] Update Claude review workflow to use latest action version and improve debug environment variable handling - Changed the action reference for `claude-code-base-action` to version `v0.0.63` for enhanced stability. - Updated the `DEBUG` environment variable to utilize a conditional expression, allowing for more flexible debugging options. These changes aim to improve the reliability and configurability of the Claude review workflow. --- .github/workflows/claude-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 0b55ec4..646c962 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -274,7 +274,7 @@ jobs: if: ${{ steps.trigger.outputs.should_run == 'true' && steps.claude_token.outputs.available == 'true' }} id: claude_review continue-on-error: true - uses: anthropics/claude-code-base-action@e8132bc5e637a42c27763fc757faa37e1ee43b34 + uses: anthropics/claude-code-base-action@v0.0.63 # NOSONAR env: ANTHROPIC_MODEL: claude-3-5-sonnet-latest with: @@ -287,7 +287,7 @@ jobs: Grep Read claude_env: | - - DEBUG: true + - DEBUG: ${{ vars.PR_REVIEW_DEBUG || 'false' }} - LOG_LEVEL: debug prompt: | REPO: ${{ github.repository }}