diff --git a/packages/mcp-core/src/tools/support/code-location.test.ts b/packages/mcp-core/src/tools/support/code-location.test.ts index 8048b3b95..a825d9d10 100644 --- a/packages/mcp-core/src/tools/support/code-location.test.ts +++ b/packages/mcp-core/src/tools/support/code-location.test.ts @@ -1,7 +1,22 @@ import { createDefaultEvent } from "@sentry/mcp-server-mocks"; import { describe, expect, it, vi } from "vitest"; -import { ApiPermissionError, type SentryApiService } from "../../api-client"; +import { + ApiPermissionError, + ApiServerError, + type SentryApiService, +} from "../../api-client"; import type { Event } from "../../api-client/types"; + +const logging = vi.hoisted(() => ({ + logIssue: vi.fn(), + logWarn: vi.fn(), +})); + +vi.mock("../../telem/logging", async (importOriginal) => ({ + ...(await importOriginal()), + logIssue: logging.logIssue, + logWarn: logging.logWarn, +})); import { resolveCodeLocation } from "./code-location"; function mockStacktraceLink() { @@ -214,6 +229,89 @@ describe("resolveCodeLocation", () => { await expect(resolve(event, getStacktraceLink)).resolves.toBeUndefined(); }); + it("logs a warning instead of an issue when the API returns a 5xx", async () => { + const { logIssue, logWarn } = logging; + logIssue.mockClear(); + logWarn.mockClear(); + try { + const event = createDefaultEvent({ + entries: [ + { + type: "exception", + data: { + values: [ + { + type: "Error", + stacktrace: { + frames: [ + { filename: "src/main.ts", lineNo: 10, inApp: true }, + ], + }, + }, + ], + }, + }, + ], + }) as Event; + const getStacktraceLink = mockStacktraceLink().mockRejectedValue( + new ApiServerError("Internal Error", 500), + ); + + await expect(resolve(event, getStacktraceLink)).resolves.toBeUndefined(); + expect(getStacktraceLink).toHaveBeenCalledOnce(); + expect(logIssue).not.toHaveBeenCalled(); + expect(logWarn).toHaveBeenCalledWith( + "Stacktrace link lookup failed; omitting code location", + expect.objectContaining({ + extra: expect.objectContaining({ + organizationSlug: "acme", + projectSlug: "backend", + status: 500, + }), + }), + ); + } finally { + vi.restoreAllMocks(); + } + }); + + it("reports unexpected errors as issues", async () => { + const { logIssue, logWarn } = logging; + logIssue.mockClear(); + logWarn.mockClear(); + try { + const event = createDefaultEvent({ + entries: [ + { + type: "exception", + data: { + values: [ + { + type: "Error", + stacktrace: { + frames: [ + { filename: "src/main.ts", lineNo: 10, inApp: true }, + ], + }, + }, + ], + }, + }, + ], + }) as Event; + const getStacktraceLink = mockStacktraceLink().mockRejectedValue( + new Error("boom"), + ); + + await expect(resolve(event, getStacktraceLink)).resolves.toBeUndefined(); + expect(getStacktraceLink).toHaveBeenCalledOnce(); + expect(logIssue).toHaveBeenCalledOnce(); + expect(logWarn).not.toHaveBeenCalled(); + } finally { + vi.restoreAllMocks(); + } + }); + it("omits a mapped path when SCM source verification fails", async () => { const event = createDefaultEvent() as Event; const getStacktraceLink = mockStacktraceLink().mockResolvedValue({ diff --git a/packages/mcp-core/src/tools/support/code-location.ts b/packages/mcp-core/src/tools/support/code-location.ts index c93d1159b..3c02e9a17 100644 --- a/packages/mcp-core/src/tools/support/code-location.ts +++ b/packages/mcp-core/src/tools/support/code-location.ts @@ -1,5 +1,6 @@ import { ApiClientError, + ApiServerError, ErrorEntrySchema, type SentryApiService, ThreadsEntrySchema, @@ -11,7 +12,7 @@ import { type Frame, findMostRelevantInAppFrame, } from "../../internal/code-location"; -import { logIssue } from "../../telem/logging"; +import { logIssue, logWarn } from "../../telem/logging"; const CODE_LOCATION_TIMEOUT_MS = 3000; @@ -89,6 +90,21 @@ export async function resolveCodeLocation({ url: sourceUrl, }; } catch (error) { + if (!controller.signal.aborted && error instanceof ApiServerError) { + // Upstream 5xx from the stacktrace-link endpoint only removes optional + // enrichment; keep it visible as a warning instead of a Sentry issue. + logWarn("Stacktrace link lookup failed; omitting code location", { + loggerScope: ["tools", "get-issue-details", "code-location"], + extra: { + organizationSlug, + projectSlug, + groupId: event.groupID, + status: error.status, + message: error.message, + }, + }); + return undefined; + } if ( !controller.signal.aborted && !(error instanceof ApiClientError) &&