fix(errors): dedupe identical messages when traversing error .cause chain (#84556)

Merged via squash.

Prepared head SHA: 46aa27fa12
Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
This commit is contained in:
Dallin Romney
2026-05-20 11:26:14 -07:00
committed by GitHub
parent 950e5c8c50
commit 447a3643c6
2 changed files with 18 additions and 4 deletions

View File

@@ -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}`));

View File

@@ -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<unknown>([err]);
// Skip causes that repeat a message already emitted (e.g. coerceToFailoverError).
const seenMessages = new Set<string>([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;