From 447a3643c69b9ed8cd6a80dd99ecc5299ea4e02a Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 20 May 2026 11:26:14 -0700 Subject: [PATCH] fix(errors): dedupe identical messages when traversing error .cause chain (#84556) Merged via squash. Prepared head SHA: 46aa27fa12e8e161cddc3c8b901a5dd6851423ca Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com> Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Reviewed-by: @altaywtf --- src/infra/errors.test.ts | 7 +++++++ src/infra/errors.ts | 15 +++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/infra/errors.test.ts b/src/infra/errors.test.ts index 4f7a6550d25..e2b6e856c9a 100644 --- a/src/infra/errors.test.ts +++ b/src/infra/errors.test.ts @@ -88,6 +88,13 @@ describe("error helpers", () => { expect(formatted).toBe("error A | error B"); }); + 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} | ${rootCause.message}`); + }); + it("redacts sensitive tokens from formatted error messages", () => { const token = "sk-abcdefghijklmnopqrstuv"; const formatted = formatErrorMessage(new Error(`Authorization: Bearer ${token}`)); diff --git a/src/infra/errors.ts b/src/infra/errors.ts index b34c6cfae63..b9f62dd3236 100644 --- a/src/infra/errors.ts +++ b/src/infra/errors.ts @@ -72,15 +72,22 @@ export function formatErrorMessage(err: unknown): string { // Traverse .cause chain to include nested error messages (e.g. grammY HttpError wraps network errors in .cause) let cause: unknown = err.cause; 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) { - formatted += ` | ${cause.message}`; - } + appendCauseMessage(cause.message); cause = cause.cause; } else if (typeof cause === "string") { - formatted += ` | ${cause}`; + appendCauseMessage(cause); break; } else { break;