From 47405dfda7400c7f88de8fa1b28957923753799d Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 24 Mar 2026 12:57:11 -0400 Subject: [PATCH] fix(auto): add EAGAIN to INFRA_ERROR_CODES to stop budget-burning retries EAGAIN (resource temporarily unavailable) is a resource exhaustion error that cannot be recovered by retrying, yet it was missing from the infra error set. This caused auto-mode to keep retrying on EAGAIN failures, burning LLM budget on guaranteed failures. Fixes #2359 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto/infra-errors.ts | 1 + .../extensions/gsd/tests/infra-error.test.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/resources/extensions/gsd/auto/infra-errors.ts b/src/resources/extensions/gsd/auto/infra-errors.ts index 724daa551..dc24a58c2 100644 --- a/src/resources/extensions/gsd/auto/infra-errors.ts +++ b/src/resources/extensions/gsd/auto/infra-errors.ts @@ -18,6 +18,7 @@ export const INFRA_ERROR_CODES: ReadonlySet = new Set([ "EDQUOT", // disk quota exceeded "EMFILE", // too many open files (process) "ENFILE", // too many open files (system) + "EAGAIN", // resource temporarily unavailable (resource exhaustion) "ECONNREFUSED", // connection refused (offline / local server down) "ENOTFOUND", // DNS lookup failed (offline / no network) "ENETUNREACH", // network unreachable (offline / no route) diff --git a/src/resources/extensions/gsd/tests/infra-error.test.ts b/src/resources/extensions/gsd/tests/infra-error.test.ts index feb5630ea..0ec65332d 100644 --- a/src/resources/extensions/gsd/tests/infra-error.test.ts +++ b/src/resources/extensions/gsd/tests/infra-error.test.ts @@ -9,11 +9,11 @@ import { isInfrastructureError, INFRA_ERROR_CODES } from "../auto/infra-errors.j test("INFRA_ERROR_CODES contains the expected codes", () => { for (const code of [ "ENOSPC", "ENOMEM", "EROFS", "EDQUOT", "EMFILE", "ENFILE", - "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH", + "EAGAIN", "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH", ]) { assert.ok(INFRA_ERROR_CODES.has(code), `missing ${code}`); } - assert.equal(INFRA_ERROR_CODES.size, 9, "unexpected extra codes"); + assert.equal(INFRA_ERROR_CODES.size, 10, "unexpected extra codes"); }); // ── isInfrastructureError: code property detection ─────────────────────────── @@ -48,6 +48,16 @@ test("detects ENFILE via code property", () => { assert.equal(isInfrastructureError(err), "ENFILE"); }); +test("detects EAGAIN via code property", () => { + const err = Object.assign(new Error("resource temporarily unavailable"), { code: "EAGAIN" }); + assert.equal(isInfrastructureError(err), "EAGAIN"); +}); + +test("detects EAGAIN in error message fallback", () => { + const err = new Error("spawn failed: EAGAIN resource temporarily unavailable"); + assert.equal(isInfrastructureError(err), "EAGAIN"); +}); + test("detects ECONNREFUSED via code property", () => { const err = Object.assign(new Error("connect ECONNREFUSED 127.0.0.1:3000"), { code: "ECONNREFUSED" }); assert.equal(isInfrastructureError(err), "ECONNREFUSED");