A network failure is printed and recorded with its reason (closes #196)
check / check (push) Waiting to run
check / check (push) Waiting to run
Node's `fetch` rejects every failure below HTTP as `fetch failed` and puts the reason on `cause`. `describeError` in `src/errors.ts` gives the message followed by the messages on the cause chain, and for an `AggregateError`, which Node gives when every address of a host fails, the message of each address. `run` prints it, and the backup logs and records it. `classify` reads that text and calls a failure transient when `hasTransportCode`, now exported by `src/retry.ts`, finds a transport errno on the chain. A failed ML data fetch keeps its error as the cause. The bounded walk along the chain moved from `src/retry.ts` to `src/errors.ts`. A truncated download's message leaves the authentication failure to its cause. Model: opus-5-5
This commit was merged in pull request #203.
This commit is contained in:
+12
-16
@@ -10,7 +10,7 @@
|
||||
// declining to retry a transient one costs a single file that the next run
|
||||
// picks up anyway. When the evidence is ambiguous, fail fast.
|
||||
|
||||
import { ApiError, TruncatedStreamError } from "./errors.js";
|
||||
import { ApiError, causeChain, TruncatedStreamError } from "./errors.js";
|
||||
|
||||
export interface RetryOptions {
|
||||
// Total calls, not retries: `attempts: 1` disables retrying.
|
||||
@@ -89,11 +89,6 @@ const TRANSPORT_CODES = new Set([
|
||||
// narrowed. See `isSafeToReplay`.
|
||||
const CONNECT_CODES = new Set(["ENOTFOUND", "EAI_AGAIN", "ECONNREFUSED"]);
|
||||
|
||||
// `cause` is an arbitrary user-settable property and nothing prevents it from
|
||||
// forming a cycle, so the walk is bounded. Hanging the process would be a
|
||||
// worse outcome than any misclassification.
|
||||
const MAX_CAUSE_DEPTH = 8;
|
||||
|
||||
// Collect every `code` in an error's cause chain. undici does not put the
|
||||
// errno on the error it throws — it hangs the underlying socket error off
|
||||
// `cause`, sometimes more than one level down — so a classifier that only read
|
||||
@@ -101,20 +96,21 @@ const MAX_CAUSE_DEPTH = 8;
|
||||
// connection permanent. `complete` is false when the walk stopped at the
|
||||
// depth limit with more of the chain still below it.
|
||||
const causeCodes = (err: unknown): { codes: string[]; complete: boolean } => {
|
||||
const { chain, complete } = causeChain(err);
|
||||
const codes: string[] = [];
|
||||
let current: unknown = err;
|
||||
for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
|
||||
if (current === null || typeof current !== "object") {
|
||||
return { codes, complete: true };
|
||||
}
|
||||
const { code, cause } = current as { code?: unknown; cause?: unknown };
|
||||
for (const link of chain) {
|
||||
const { code } = link as { code?: unknown };
|
||||
if (typeof code === "string") codes.push(code);
|
||||
if (cause === current) return { codes, complete: true };
|
||||
current = cause;
|
||||
}
|
||||
return { codes, complete: current === null || typeof current !== "object" };
|
||||
return { codes, complete };
|
||||
};
|
||||
|
||||
// Does the error's cause chain hold the errno of a transport failure, one of
|
||||
// `TRANSPORT_CODES`? `isRetryable` retries such an error, and `classify` in
|
||||
// `src/backup.ts` calls it transient.
|
||||
export const hasTransportCode = (err: unknown): boolean =>
|
||||
causeCodes(err).codes.some((code) => TRANSPORT_CODES.has(code));
|
||||
|
||||
const isAbort = (err: unknown): boolean => {
|
||||
if (err === null || typeof err !== "object") return false;
|
||||
const { name } = err as { name?: unknown };
|
||||
@@ -158,7 +154,7 @@ export const isRetryable = (err: unknown): boolean => {
|
||||
// have succeeded; the cost of the imprecision is bounded by the attempt
|
||||
// count.
|
||||
if (err instanceof TypeError) return true;
|
||||
return causeCodes(err).codes.some((code) => TRANSPORT_CODES.has(code));
|
||||
return hasTransportCode(err);
|
||||
};
|
||||
|
||||
// Could the first attempt already have taken effect on the server?
|
||||
|
||||
Reference in New Issue
Block a user