feat(ai-http): log detailed fetch error cause chain

Node's fetch returns a generic "fetch failed" error, hiding the actual
reason (e.g., ECONNRESET, timeout) in the error's cause chain. This
change extracts up to three levels of the cause, formats each with its
code and message, and includes the chain in the warning log, making
failures more actionable.
This commit is contained in:
claude_code
2026-06-23 03:01:10 +03:00
parent fd66ee6cce
commit b7abb7ea01

View File

@@ -128,8 +128,21 @@ export const aiFetch: typeof fetch = async (input, init) => {
return res; return res;
} catch (err) { } catch (err) {
const ms = Math.round(performance.now() - startedAt); const ms = Math.round(performance.now() - startedAt);
// Node's fetch reports a generic "fetch failed"; the real reason (e.g. an
// undici SocketError with .code ECONNRESET / UND_ERR_SOCKET /
// UND_ERR_*TIMEOUT) lives in err.cause (sometimes nested one level deeper).
// Surface the code+message of the cause chain so the failure is actionable.
const parts: string[] = [];
let cur: unknown = err;
for (let depth = 0; cur && depth < 3; depth++) {
const e = cur as { code?: string; message?: string; cause?: unknown };
const code = e.code ? `[${e.code}] ` : '';
const msg = e.message ?? String(e);
parts.push(`${code}${msg}`);
cur = e.cause;
}
logger.warn( logger.warn(
`provider request #${id} x after ${ms}ms: ${(err as Error)?.message ?? String(err)}`, `provider request #${id} x after ${ms}ms: ${parts.join(' <- ')}`,
); );
throw err; throw err;
} }