A network failure is printed and recorded with its reason (closes #196)
check / check (push) Canceled after 0s

Node's `fetch` rejects every failure below HTTP as `fetch failed` and puts
the reason on `cause`, so quak printed and recorded only that, and
`failures.json` called the failure `unknown`. `describeError` in
`src/errors.ts` gives the message followed by the messages on the cause
chain; `run` prints it, and the backup logs and records it. `classify`
calls a failure transient when `hasTransportCode`, now exported by
`src/retry.ts`, finds a transport errno on the chain. The bounded walk
along the chain moved from `src/retry.ts` to `src/errors.ts`, so both
use one walk.

Every message the backup logs or records, `imageMetadataError` and
`mlDataError` included, now carries its causes too.

Model: opus-5-5
This commit is contained in:
2026-10-15 23:13:31 +00:00
parent 3412e42af5
commit e93bb06330
8 changed files with 198 additions and 32 deletions
+29
View File
@@ -48,6 +48,35 @@ describe("run", () => {
);
});
// Node's `fetch` rejects every failure below HTTP with a TypeError that
// says only "fetch failed", and hangs the reason, with its errno, off
// `cause`. The printed line carries the reason, so a cron log tells a
// refused connection from a name that did not resolve.
it("prints the reason a fetch failed, from the error's cause", async () => {
const refused = new TypeError("fetch failed", {
cause: Object.assign(
new Error("connect ECONNREFUSED 1.2.3.4:443"),
{ code: "ECONNREFUSED" },
),
});
const result = await runToExit(Promise.reject(refused));
expect(result).toEqual({
code: 1,
stdout: "",
stderr: "quak: fetch failed: connect ECONNREFUSED 1.2.3.4:443\n",
});
});
// An error that already quotes its cause's message is printed as it is.
it("does not print a cause's message twice", async () => {
const reset = new Error("read ECONNRESET");
const wrapped = new Error(`download: file 7: ${reset.message}`, {
cause: reset,
});
const result = await runToExit(Promise.reject(wrapped));
expect(result.stderr).toBe("quak: download: file 7: read ECONNRESET\n");
});
it("prints a thrown value that is not an Error", async () => {
const result = await runToExit(Promise.reject("offline"));
expect(result).toEqual({