From 15db2b3c79c0681556c056e9bc3f61fd3fc0347d Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Wed, 6 Aug 2025 08:21:29 -0700 Subject: [PATCH 1/4] feat: add inline comment MCP server for experimental review mode (#414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add inline comment MCP server for experimental review mode - Create standalone inline PR comments without review workflow - Support single-line and multi-line comments - Auto-install server when in experimental review mode - Uses octokit.rest.pulls.createReviewComment() directly * docs: clarify GitHub code suggestion syntax in inline comment server Add clear documentation that suggestion blocks replace the entire selected line range and must be syntactically complete drop-in replacements. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- src/mcp/github-inline-comment-server.ts | 178 ++++++++++++++++++++++++ src/mcp/install-mcp-server.ts | 18 +++ src/modes/review/index.ts | 69 +-------- 3 files changed, 201 insertions(+), 64 deletions(-) create mode 100644 src/mcp/github-inline-comment-server.ts diff --git a/src/mcp/github-inline-comment-server.ts b/src/mcp/github-inline-comment-server.ts new file mode 100644 index 0000000..28a8658 --- /dev/null +++ b/src/mcp/github-inline-comment-server.ts @@ -0,0 +1,178 @@ +#!/usr/bin/env node +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { createOctokit } from "../github/api/client"; + +// Get repository and PR information from environment variables +const REPO_OWNER = process.env.REPO_OWNER; +const REPO_NAME = process.env.REPO_NAME; +const PR_NUMBER = process.env.PR_NUMBER; + +if (!REPO_OWNER || !REPO_NAME || !PR_NUMBER) { + console.error( + "Error: REPO_OWNER, REPO_NAME, and PR_NUMBER environment variables are required", + ); + process.exit(1); +} + +// GitHub Inline Comment MCP Server - Provides inline PR comment functionality +// Provides an inline comment tool without exposing full PR review capabilities, so that +// Claude can't accidentally approve a PR +const server = new McpServer({ + name: "GitHub Inline Comment Server", + version: "0.0.1", +}); + +server.tool( + "create_inline_comment", + "Create an inline comment on a specific line or lines in a PR file", + { + path: z + .string() + .describe("The file path to comment on (e.g., 'src/index.js')"), + body: z + .string() + .describe( + "The comment text (supports markdown and GitHub code suggestion blocks). " + + "For code suggestions, use: ```suggestion\\nreplacement code\\n```. " + + "IMPORTANT: The suggestion block will REPLACE the ENTIRE line range (single line or startLine to line). " + + "Ensure the replacement is syntactically complete and valid - it must work as a drop-in replacement for the selected lines.", + ), + line: z + .number() + .optional() + .describe( + "Line number for single-line comments (required if startLine is not provided)", + ), + startLine: z + .number() + .optional() + .describe( + "Start line for multi-line comments (use with line parameter for the end line)", + ), + side: z + .enum(["LEFT", "RIGHT"]) + .optional() + .default("RIGHT") + .describe( + "Side of the diff to comment on: LEFT (old code) or RIGHT (new code)", + ), + commit_id: z + .string() + .optional() + .describe( + "Specific commit SHA to comment on (defaults to latest commit)", + ), + }, + async ({ path, body, line, startLine, side, commit_id }) => { + try { + const githubToken = process.env.GITHUB_TOKEN; + + if (!githubToken) { + throw new Error("GITHUB_TOKEN environment variable is required"); + } + + const owner = REPO_OWNER; + const repo = REPO_NAME; + const pull_number = parseInt(PR_NUMBER, 10); + + const octokit = createOctokit(githubToken).rest; + + // Validate that either line or both startLine and line are provided + if (!line && !startLine) { + throw new Error( + "Either 'line' for single-line comments or both 'startLine' and 'line' for multi-line comments must be provided", + ); + } + + // If only line is provided, it's a single-line comment + // If both startLine and line are provided, it's a multi-line comment + const isSingleLine = !startLine; + + const pr = await octokit.pulls.get({ + owner, + repo, + pull_number, + }); + + const params: Parameters< + typeof octokit.rest.pulls.createReviewComment + >[0] = { + owner, + repo, + pull_number, + body, + path, + side: side || "RIGHT", + commit_id: commit_id || pr.data.head.sha, + }; + + if (isSingleLine) { + // Single-line comment + params.line = line; + } else { + // Multi-line comment + params.start_line = startLine; + params.start_side = side || "RIGHT"; + params.line = line; + } + + const result = await octokit.rest.pulls.createReviewComment(params); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + success: true, + comment_id: result.data.id, + html_url: result.data.html_url, + path: result.data.path, + line: result.data.line || result.data.original_line, + message: `Inline comment created successfully on ${path}${isSingleLine ? ` at line ${line}` : ` from line ${startLine} to ${line}`}`, + }, + null, + 2, + ), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + + // Provide more helpful error messages for common issues + let helpMessage = ""; + if (errorMessage.includes("Validation Failed")) { + helpMessage = + "\n\nThis usually means the line number doesn't exist in the diff or the file path is incorrect. Make sure you're commenting on lines that are part of the PR's changes."; + } else if (errorMessage.includes("Not Found")) { + helpMessage = + "\n\nThis usually means the PR number, repository, or file path is incorrect."; + } + + return { + content: [ + { + type: "text", + text: `Error creating inline comment: ${errorMessage}${helpMessage}`, + }, + ], + error: errorMessage, + isError: true, + }; + } + }, +); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + process.on("exit", () => { + server.close(); + }); +} + +runServer().catch(console.error); diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts index 61b11d6..9a87f12 100644 --- a/src/mcp/install-mcp-server.ts +++ b/src/mcp/install-mcp-server.ts @@ -111,6 +111,24 @@ export async function prepareMcpConfig( }; } + // Include inline comment server for experimental review mode + if (context.inputs.mode === "experimental-review" && context.isPR) { + baseMcpConfig.mcpServers.github_inline_comment = { + command: "bun", + args: [ + "run", + `${process.env.GITHUB_ACTION_PATH}/src/mcp/github-inline-comment-server.ts`, + ], + env: { + GITHUB_TOKEN: githubToken, + REPO_OWNER: owner, + REPO_NAME: repo, + PR_NUMBER: context.entityNumber?.toString() || "", + GITHUB_API_URL: GITHUB_API_URL, + }, + }; + } + // Only add CI server if we have actions:read permission and we're in a PR context const hasActionsReadPermission = context.inputs.additionalPermissions.get("actions") === "read"; diff --git a/src/modes/review/index.ts b/src/modes/review/index.ts index 4213c1c..e53f8f8 100644 --- a/src/modes/review/index.ts +++ b/src/modes/review/index.ts @@ -60,20 +60,8 @@ export const reviewMode: Mode = { getAllowedTools() { return [ - // Context tools - to know who the current user is - "mcp__github__get_me", - // Core review tools - "mcp__github__create_pending_pull_request_review", - "mcp__github__add_comment_to_pending_review", - "mcp__github__submit_pending_pull_request_review", - "mcp__github__delete_pending_pull_request_review", - "mcp__github__create_and_submit_pull_request_review", - // Comment tools - "mcp__github__add_issue_comment", - // PR information tools - "mcp__github__get_pull_request", - "mcp__github__get_pull_request_reviews", - "mcp__github__get_pull_request_status", + "Bash(gh issue comment:*)", + "mcp__github_inline_comment__create_inline_comment", ]; }, @@ -163,17 +151,13 @@ REVIEW MODE WORKFLOW: 1. First, understand the PR context: - You are reviewing PR #${eventData.isPR && eventData.prNumber ? eventData.prNumber : "[PR number]"} in ${context.repository} - - Use mcp__github__get_pull_request to get PR metadata - Use the Read, Grep, and Glob tools to examine the modified files directly from disk - This provides the full context and latest state of the code - Look at the changed_files section above to see which files were modified -2. Create a pending review: - - Use mcp__github__create_pending_pull_request_review to start your review - - This allows you to batch comments before submitting - -3. Add inline comments: - - Use mcp__github__add_comment_to_pending_review for each issue or suggestion +2. Add comments: + - use Bash(gh issue comment:*) to add top-level comments + - Use mcp__github_inline_comment__create_inline_comment to add inline comments (prefer this where possible) - Parameters: * path: The file path (e.g., "src/index.js") * line: Line number for single-line comments @@ -182,49 +166,6 @@ REVIEW MODE WORKFLOW: * subjectType: "line" for line-level comments * body: Your comment text - - When to use multi-line comments: - * When replacing multiple consecutive lines - * When the fix requires changes across several lines - * Example: To replace lines 19-20, use startLine: 19, line: 20 - - - For code suggestions, use this EXACT format in the body: - \`\`\`suggestion - corrected code here - \`\`\` - - CRITICAL: GitHub suggestion blocks must ONLY contain the replacement for the specific line(s) being commented on: - - For single-line comments: Replace ONLY that line - - For multi-line comments: Replace ONLY the lines in the range - - Do NOT include surrounding context or function signatures - - Do NOT suggest changes that span beyond the commented lines - - Example for line 19 \`var name = user.name;\`: - WRONG: - \\\`\\\`\\\`suggestion - function processUser(user) { - if (!user) throw new Error('Invalid user'); - const name = user.name; - \\\`\\\`\\\` - - CORRECT: - \\\`\\\`\\\`suggestion - const name = user.name; - \\\`\\\`\\\` - - For validation suggestions, comment on the function declaration line or create separate comments for each concern. - -4. Submit your review: - - Use mcp__github__submit_pending_pull_request_review - - Parameters: - * event: "COMMENT" (general feedback), "REQUEST_CHANGES" (issues found), or "APPROVE" (if appropriate) - * body: Write a comprehensive review summary that includes: - - Overview of what was reviewed (files, scope, focus areas) - - Summary of all issues found (with counts by severity if applicable) - - Key recommendations and action items - - Highlights of good practices observed - - Overall assessment and recommendation - - The body should be detailed and informative since it's the main review content - - Structure the body with clear sections using markdown headers REVIEW GUIDELINES: From 55fb6a96d0c1b769be8154a856e9ae40871d3092 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 6 Aug 2025 19:59:40 +0000 Subject: [PATCH 2/4] chore: bump Claude Code version to 1.0.70 --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index b7dfe22..00441c0 100644 --- a/action.yml +++ b/action.yml @@ -172,7 +172,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - bun install -g @anthropic-ai/claude-code@1.0.69 + bun install -g @anthropic-ai/claude-code@1.0.70 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/action.yml b/base-action/action.yml index 250db3d..a9d626a 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: bun install -g @anthropic-ai/claude-code@1.0.69 + run: bun install -g @anthropic-ai/claude-code@1.0.70 - name: Run Claude Code Action shell: bash From 6debac392b556f15d25e39e33b021d6e24a48614 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Thu, 7 Aug 2025 05:22:15 +0100 Subject: [PATCH 3/4] Go with Opus 4.1 (#420) --- .github/workflows/claude.yml | 2 +- base-action/README.md | 4 ++-- base-action/test/setup-claude-code-settings.test.ts | 4 ++-- docs/configuration.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 35d9fe3..99407a3 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -36,4 +36,4 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} allowed_tools: "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)" custom_instructions: "You have also been granted tools for editing files and running bun commands (install, run, test, typecheck) for testing your changes: bun install, bun test, bun run format, bun typecheck." - model: "claude-opus-4-20250514" + model: "claude-opus-4-1-20250805" diff --git a/base-action/README.md b/base-action/README.md index 2166511..2a9a863 100644 --- a/base-action/README.md +++ b/base-action/README.md @@ -69,7 +69,7 @@ Add the following to your workflow file: uses: anthropics/claude-code-base-action@beta with: prompt: "Review and fix TypeScript errors" - model: "claude-opus-4-20250514" + model: "claude-opus-4-1-20250805" fallback_model: "claude-sonnet-4-20250514" allowed_tools: "Bash(git:*),View,GlobTool,GrepTool,BatchTool" anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} @@ -217,7 +217,7 @@ Provide the settings configuration directly as a JSON string: prompt: "Your prompt here" settings: | { - "model": "claude-opus-4-20250514", + "model": "claude-opus-4-1-20250805", "env": { "DEBUG": "true", "API_URL": "https://api.example.com" diff --git a/base-action/test/setup-claude-code-settings.test.ts b/base-action/test/setup-claude-code-settings.test.ts index c5a103b..19cf0cd 100644 --- a/base-action/test/setup-claude-code-settings.test.ts +++ b/base-action/test/setup-claude-code-settings.test.ts @@ -134,7 +134,7 @@ describe("setupClaudeCodeSettings", () => { // Then, add new settings const newSettings = JSON.stringify({ newKey: "newValue", - model: "claude-opus-4-20250514", + model: "claude-opus-4-1-20250805", }); await setupClaudeCodeSettings(newSettings, testHomeDir); @@ -145,7 +145,7 @@ describe("setupClaudeCodeSettings", () => { expect(settings.enableAllProjectMcpServers).toBe(true); expect(settings.existingKey).toBe("existingValue"); expect(settings.newKey).toBe("newValue"); - expect(settings.model).toBe("claude-opus-4-20250514"); + expect(settings.model).toBe("claude-opus-4-1-20250805"); }); test("should copy slash commands to .claude directory when path provided", async () => { diff --git a/docs/configuration.md b/docs/configuration.md index 5d3d125..ec0f317 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -252,7 +252,7 @@ You can provide Claude Code settings to customize behavior such as model selecti with: settings: | { - "model": "claude-opus-4-20250514", + "model": "claude-opus-4-1-20250805", "env": { "DEBUG": "true", "API_URL": "https://api.example.com" From 7afc84818658042af4da4187f9b77ae301147aa2 Mon Sep 17 00:00:00 2001 From: Aner Cohen <89394977+AnerRiskified@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:56:30 +0300 Subject: [PATCH 4/4] fix: improve GitHub suggestion guidelines in review mode to prevent code duplication (#422) * fix: prevent duplicate function signatures in review mode suggestions This fixes a critical bug in the experimental review mode where GitHub suggestions could create duplicate function signatures when applied. The issue occurred because: - GitHub suggestions REPLACE the entire selected line range - Claude wasn't aware of this behavior and would include the function signature in multi-line suggestions, causing duplication Changes: - Added detailed instructions about GitHub's line replacement behavior - Provided clear examples for single-line vs multi-line suggestions - Added explicit warnings about common mistakes (duplicate signatures) - Improved code readability by using a codeBlock variable instead of escaped backticks in template strings This ensures Claude creates syntactically correct suggestions that won't break code when applied through GitHub's suggestion feature. * chore: format --- src/modes/review/index.ts | 62 +++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/modes/review/index.ts b/src/modes/review/index.ts index e53f8f8..eb520cc 100644 --- a/src/modes/review/index.ts +++ b/src/modes/review/index.ts @@ -103,6 +103,9 @@ export const reviewMode: Mode = { ? formatBody(contextData.body, imageUrlMap) : "No description provided"; + // Using a variable for code blocks to avoid escaping backticks in the template string + const codeBlock = "```"; + return `You are Claude, an AI assistant specialized in code reviews for GitHub pull requests. You are operating in REVIEW MODE, which means you should focus on providing thorough code review feedback using GitHub MCP tools for inline comments and suggestions. @@ -155,17 +158,46 @@ REVIEW MODE WORKFLOW: - This provides the full context and latest state of the code - Look at the changed_files section above to see which files were modified -2. Add comments: - - use Bash(gh issue comment:*) to add top-level comments - - Use mcp__github_inline_comment__create_inline_comment to add inline comments (prefer this where possible) - - Parameters: - * path: The file path (e.g., "src/index.js") - * line: Line number for single-line comments - * startLine & line: For multi-line comments (startLine is the first line, line is the last) - * side: "LEFT" (old code) or "RIGHT" (new code) - * subjectType: "line" for line-level comments - * body: Your comment text +2. Create review comments using GitHub MCP tools: + - Use Bash(gh issue comment:*) for general PR-level comments + - Use mcp__github_inline_comment__create_inline_comment for line-specific feedback (strongly preferred) +3. When creating inline comments with suggestions: + CRITICAL: GitHub's suggestion blocks REPLACE the ENTIRE line range you select + - For single-line comments: Use 'line' parameter only + - For multi-line comments: Use both 'startLine' and 'line' parameters + - The 'body' parameter should contain your comment and/or suggestion block + + How to write code suggestions correctly: + a) To remove a line (e.g., removing console.log on line 22): + - Set line: 22 + - Body: ${codeBlock}suggestion + ${codeBlock} + (Empty suggestion block removes the line) + + b) To modify a single line (e.g., fixing line 22): + - Set line: 22 + - Body: ${codeBlock}suggestion + await this.emailInput.fill(email); + ${codeBlock} + + c) To replace multiple lines (e.g., lines 21-23): + - Set startLine: 21, line: 23 + - Body must include ALL lines being replaced: + ${codeBlock}suggestion + async typeEmail(email: string): Promise { + await this.emailInput.fill(email); + } + ${codeBlock} + + COMMON MISTAKE TO AVOID: + Never duplicate code in suggestions. For example, DON'T do this: + ${codeBlock}suggestion + async typeEmail(email: string): Promise { + async typeEmail(email: string): Promise { // WRONG: Duplicate signature! + await this.emailInput.fill(email); + } + ${codeBlock} REVIEW GUIDELINES: @@ -179,13 +211,11 @@ REVIEW GUIDELINES: - Provide: * Specific, actionable feedback - * Code suggestions when possible (following GitHub's format exactly) - * Clear explanations of issues - * Constructive criticism + * Code suggestions using the exact format described above + * Clear explanations of issues found + * Constructive criticism with solutions * Recognition of good practices - * For complex changes that require multiple modifications: - - Create separate comments for each logical change - - Or explain the full solution in text without a suggestion block + * For complex changes: Create separate inline comments for each logical change - Communication: * All feedback goes through GitHub's review system