From e21111470d2ccfa0637aef5ad82307e36adbcd42 Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Sun, 12 Jul 2026 12:15:33 -0700 Subject: [PATCH] fix(observability): avoid mutating captured error names --- src/selfhost/sentry.ts | 19 ++++++++++++------- test/unit/selfhost-sentry.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/selfhost/sentry.ts b/src/selfhost/sentry.ts index cffa023db..a9c8e1e87 100644 --- a/src/selfhost/sentry.ts +++ b/src/selfhost/sentry.ts @@ -431,16 +431,21 @@ export async function buildSentryOpenTelemetryBridge(): Promise { expect(lastCapturedError().name).toBe("ai_review_failed"); }); + it("captureError/captureReviewFailure with an eventName never mutate caught errors that reject name writes", async () => { + await initSentry({ SENTRY_DSN: "d" } as unknown as NodeJS.ProcessEnv); + + const timeoutError = new DOMException("signal timed out", "TimeoutError"); + captureError(timeoutError, { kind: "fetch_timeout" }, "github_fetch_failed"); + + expect(timeoutError.name).toBe("TimeoutError"); + expect(lastCapturedError()).not.toBe(timeoutError); + expect(lastCapturedError().name).toBe("github_fetch_failed"); + expect(lastCapturedError().message).toBe("signal timed out"); + expect(lastCapturedError().cause).toBe(timeoutError); + + const frozenError = Object.freeze(new Error("review failed")); + captureReviewFailure(frozenError, { repo: "o/r" }, "ai_review_failed"); + + expect(frozenError.name).toBe("Error"); + expect(lastCapturedError()).not.toBe(frozenError); + expect(lastCapturedError().name).toBe("ai_review_failed"); + expect(lastCapturedError().message).toBe("review failed"); + expect(lastCapturedError().cause).toBe(frozenError); + }); + it("adds active OTEL trace ids to captured Sentry events", async () => { await initSentry({ SENTRY_DSN: "d" } as unknown as NodeJS.ProcessEnv); otelMocks.currentOtelTraceIds.mockReturnValue({