From 77788a1b7e6d5f8f94526844d67aabba0866bd52 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 17:51:19 -0700 Subject: [PATCH] fix(gsd): classify plain connection-error as transient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #3594 — CONNECTION_RE required specific suffixes after connection, so plain Connection error fell through to unknown causing indefinite auto-mode pause. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/error-classifier.ts | 2 +- src/resources/extensions/gsd/tests/provider-errors.test.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/error-classifier.ts b/src/resources/extensions/gsd/error-classifier.ts index 604167451..fc5a543f7 100644 --- a/src/resources/extensions/gsd/error-classifier.ts +++ b/src/resources/extensions/gsd/error-classifier.ts @@ -47,7 +47,7 @@ const RATE_LIMIT_RE = /rate.?limit|too many requests|429/i; const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i; const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i; // ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first). -const CONNECTION_RE = /terminated|connection.?refused|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i; +const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i; // Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+". // This eliminates the need to enumerate every error message variant individually. const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i; diff --git a/src/resources/extensions/gsd/tests/provider-errors.test.ts b/src/resources/extensions/gsd/tests/provider-errors.test.ts index e4ec992d4..3d658102a 100644 --- a/src/resources/extensions/gsd/tests/provider-errors.test.ts +++ b/src/resources/extensions/gsd/tests/provider-errors.test.ts @@ -101,6 +101,13 @@ test("classifyError detects quota exceeded as permanent", () => { assert.ok(!isTransient(result)); }); +test("classifyError treats plain 'Connection error.' as transient connection failure (#3594)", () => { + const result = classifyError("Connection error."); + assert.ok(isTransient(result)); + assert.equal(result.kind, "connection"); + assert.ok("retryAfterMs" in result && result.retryAfterMs === 15_000); +}); + test("classifyError treats unknown error as not transient", () => { const result = classifyError("something went wrong"); assert.ok(!isTransient(result));