From af121b0f47a06e4ca86acaa6971ae72eed4dc66f Mon Sep 17 00:00:00 2001 From: "sentry[bot]" <39604003+sentry[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:49:05 +0000 Subject: [PATCH] fix(tools): Prevent logging expected ApiPermissionError --- .../src/tools/catalog/update-issue.test.ts | 58 ++++++++++++++++++- .../src/tools/catalog/update-issue.ts | 5 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/mcp-core/src/tools/catalog/update-issue.test.ts b/packages/mcp-core/src/tools/catalog/update-issue.test.ts index 4710702a4..652507d70 100644 --- a/packages/mcp-core/src/tools/catalog/update-issue.test.ts +++ b/packages/mcp-core/src/tools/catalog/update-issue.test.ts @@ -1,7 +1,12 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { http, HttpResponse } from "msw"; import { issueFixture, mswServer } from "@sentry/mcp-server-mocks"; import updateIssue from "./update-issue.js"; +import { logIssue } from "../../telem/logging"; + +vi.mock("../../telem/logging", () => ({ + logIssue: vi.fn(), +})); type MockIssue = typeof issueFixture; @@ -22,6 +27,7 @@ function createIssue(overrides: Partial = {}): MockIssue { afterEach(() => { mswServer.resetHandlers(); + vi.clearAllMocks(); }); describe("update_issue", () => { @@ -1023,4 +1029,54 @@ describe("update_issue", () => { // Comment failure should be reported gracefully, not thrown expect(result).toContain("**Comment not posted**"); }); + + it("does not call logIssue when comment posting fails with a 403 permission error", async () => { + const currentIssue = createIssue({ + status: "unresolved", + statusDetails: {}, + }); + const updatedIssue = createIssue({ + status: "resolved", + statusDetails: {}, + }); + + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/", + () => HttpResponse.json(currentIssue), + ), + http.put( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/", + () => HttpResponse.json(updatedIssue), + ), + http.post( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/notes/", + () => + HttpResponse.json( + { detail: "You do not have permission to perform this action." }, + { status: 403 }, + ), + ), + ); + + const result = await updateIssue.handler( + { + organizationSlug: "sentry-mcp-evals", + issueId: "CLOUDFLARE-MCP-41", + status: "resolved", + assignedTo: undefined, + issueUrl: undefined, + regionUrl: null, + reason: "Resolving because fix deployed", + }, + serverContext, + ); + + // Update succeeded — output should show it + expect(result).toContain("**Status**: unresolved → **resolved**"); + // Comment failure should be reported gracefully, not thrown + expect(result).toContain("**Comment not posted**"); + // Expected 403 permission errors must NOT be logged as Sentry issues + expect(logIssue).not.toHaveBeenCalled(); + }); }); diff --git a/packages/mcp-core/src/tools/catalog/update-issue.ts b/packages/mcp-core/src/tools/catalog/update-issue.ts index 96ace120c..a9a35834f 100644 --- a/packages/mcp-core/src/tools/catalog/update-issue.ts +++ b/packages/mcp-core/src/tools/catalog/update-issue.ts @@ -8,6 +8,7 @@ import { import { formatAssignedTo } from "../../internal/tool-helpers/formatting"; import { logIssue } from "../../telem/logging"; import { UserInputError } from "../../errors"; +import { ApiClientError } from "../../api-client"; import type { Issue } from "../../api-client/types"; import type { ServerContext } from "../../types"; import { @@ -599,7 +600,9 @@ async function tryPostReasonComment( }); return { posted: true }; } catch (error) { - logIssue(error); + if (!(error instanceof ApiClientError)) { + logIssue(error); + } return { posted: false, error: error instanceof Error ? error.message : String(error),