diff --git a/src/infra/errors.test.ts b/src/infra/errors.test.ts index 5f8d274a628..e2b6e856c9a 100644 --- a/src/infra/errors.test.ts +++ b/src/infra/errors.test.ts @@ -88,10 +88,11 @@ describe("error helpers", () => { expect(formatted).toBe("error A | error B"); }); - it("dedupes causes whose message repeats the parent (e.g. FailoverError wrapping)", () => { - const inner = new Error('No API key found for provider "openai-codex".'); + it("dedupes repeated cause messages while preserving deeper distinct causes", () => { + const rootCause = new Error("provider auth lookup failed"); + const inner = new Error('No API key found for provider "openai-codex".', { cause: rootCause }); const wrapper = new Error(inner.message, { cause: inner }); - expect(formatErrorMessage(wrapper)).toBe(inner.message); + expect(formatErrorMessage(wrapper)).toBe(`${inner.message} | ${rootCause.message}`); }); it("redacts sensitive tokens from formatted error messages", () => { diff --git a/src/infra/errors.ts b/src/infra/errors.ts index 07a3ffcc68f..b9f62dd3236 100644 --- a/src/infra/errors.ts +++ b/src/infra/errors.ts @@ -74,18 +74,20 @@ export function formatErrorMessage(err: unknown): string { const seen = new Set([err]); // Skip causes that repeat a message already emitted (e.g. coerceToFailoverError). const seenMessages = new Set([formatted]); + const appendCauseMessage = (message: string): void => { + if (!message || seenMessages.has(message)) { + return; + } + formatted += ` | ${message}`; + seenMessages.add(message); + }; while (cause && !seen.has(cause)) { seen.add(cause); if (cause instanceof Error) { - if (cause.message && !seenMessages.has(cause.message)) { - formatted += ` | ${cause.message}`; - seenMessages.add(cause.message); - } + appendCauseMessage(cause.message); cause = cause.cause; } else if (typeof cause === "string") { - if (!seenMessages.has(cause)) { - formatted += ` | ${cause}`; - } + appendCauseMessage(cause); break; } else { break;