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);