From 84cf34ff1f810a902414777e13a208239d3dd874 Mon Sep 17 00:00:00 2001 From: Vlad Mocanu Date: Thu, 26 Feb 2026 09:51:25 +0200 Subject: [PATCH] fix: route Forgejo review comment updates to issue comment API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Forgejo, the tracking comment for pull_request_review_comment events is always created as a regular issue comment (because Forgejo's webhook payload lacks the top-level comment object). The MCP server was incorrectly setting isPullRequestReviewComment=true based on the event name, causing every update to try pulls.updateReviewComment first — which 404'd and silently swallowed Claude's responses. Three fixes: - MCP server: detect Forgejo and always use issue comment path - update-comment-link: skip pulls.getReviewComment on Forgejo (saves one wasted 404 API call) - update-claude-comment: expand fallback to also catch 422 (defense in depth for Forgejo edge cases) Co-Authored-By: Claude Opus 4.6 --- src/entrypoints/update-comment-link.ts | 12 +++++++----- .../operations/comments/update-claude-comment.ts | 10 ++++++++-- src/mcp/github-comment-server.ts | 12 +++++++++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index ec94b3b..de08d2c 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -65,11 +65,13 @@ export async function updateCommentLink( try { // GitHub has separate ID namespaces for review comments and issue comments // We need to use the correct API based on the event type - if (isPullRequestReviewCommentEvent(context)) { - // For PR review comments, try the pulls API first. - // On Forgejo, the initial comment may have been created as a regular - // issue comment (because the payload lacks comment.id), so the pulls - // endpoint may 404. Fall through to issue comment fetch in that case. + // + // On Forgejo, the tracking comment is always an issue comment because + // Forgejo's pull_request_review_comment payload lacks the top-level + // `comment` object (so createInitialComment falls back to + // issues.createComment). Skip the pulls API attempt on Forgejo to avoid + // a wasted 404 round-trip. + if (isPullRequestReviewCommentEvent(context) && platform !== "forgejo") { try { console.log(`Fetching PR review comment ${commentId}`); const { data: prComment } = await octokit.rest.pulls.getReviewComment({ diff --git a/src/github/operations/comments/update-claude-comment.ts b/src/github/operations/comments/update-claude-comment.ts index d6f4bd1..f39f7e3 100644 --- a/src/github/operations/comments/update-claude-comment.ts +++ b/src/github/operations/comments/update-claude-comment.ts @@ -49,8 +49,14 @@ export async function updateClaudeComment( }); } } catch (error: any) { - // If PR review comment update fails with 404, fall back to issue comment API - if (isPullRequestReviewComment && error.status === 404) { + // If PR review comment update fails, fall back to issue comment API. + // 404 = comment ID not in PR review namespace (expected on Forgejo where + // tracking comments are always issue comments). + // 422 = some Forgejo versions return Unprocessable Entity instead of 404. + if ( + isPullRequestReviewComment && + (error.status === 404 || error.status === 422) + ) { response = await octokit.rest.issues.updateComment({ owner, repo, diff --git a/src/mcp/github-comment-server.ts b/src/mcp/github-comment-server.ts index ef6728c..4cee729 100644 --- a/src/mcp/github-comment-server.ts +++ b/src/mcp/github-comment-server.ts @@ -52,8 +52,18 @@ server.tool( baseUrl: GITHUB_API_URL, }); + // The tracking comment is always created as a regular issue comment on + // Forgejo because Forgejo's pull_request_review_comment payload lacks the + // top-level `comment` object (so createInitialComment falls back to + // issues.createComment). On GitHub, the tracking comment IS a review + // comment reply (created via pulls.createReplyForReviewComment). + // + // Setting this wrong causes every MCP update to first try + // pulls.updateReviewComment (404), and if the fallback doesn't trigger, + // Claude's responses never reach the comment. + const isForgejo = (process.env.GITHUB_API_URL || "").includes("/api/v1"); const isPullRequestReviewComment = - eventName === "pull_request_review_comment"; + eventName === "pull_request_review_comment" && !isForgejo; const sanitizedBody = sanitizeContent(body);