From 89fbd197ea9df89931b23b9f057321735b987e95 Mon Sep 17 00:00:00 2001 From: Daniel Dali Date: Tue, 14 Apr 2026 10:56:40 +0100 Subject: [PATCH] fix(agents): classify replay-invalid connection mismatches --- .../pi-embedded-helpers.formatassistanterrortext.test.ts | 6 ++++++ .../pi-embedded-helpers.isbillingerrormessage.test.ts | 3 +++ src/agents/pi-embedded-helpers/errors.ts | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts b/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts index d1462e41a1f..56063b82c81 100644 --- a/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts +++ b/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts @@ -70,6 +70,12 @@ describe("formatAssistantErrorText", () => { expect(result).toContain("Session history looks corrupted"); expect(result).toContain("/new"); }); + it("returns a recovery hint for replay-invalid connection mismatch errors", () => { + const msg = makeAssistantError("401 input item ID does not belong to this connection"); + const result = formatAssistantErrorText(msg); + expect(result).toContain("Session history or replay state is invalid"); + expect(result).toContain("/new"); + }); it("handles JSON-wrapped role errors", () => { const msg = makeAssistantError('{"error":{"message":"400 Incorrect role information"}}'); const result = formatAssistantErrorText(msg); diff --git a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts index a78cf977729..1ac3fd0a26a 100644 --- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts +++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts @@ -1195,6 +1195,9 @@ describe("classifyProviderRuntimeFailureKind", () => { expect(classifyProviderRuntimeFailureKind("tool_use.input: Field required")).toBe( "replay_invalid", ); + expect( + classifyProviderRuntimeFailureKind("401 input item ID does not belong to this connection"), + ).toBe("replay_invalid"); }); it("does not classify generic config errors that mention proxy settings as proxy failures", () => { diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index 68deb227ae0..ce674353e60 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -320,7 +320,7 @@ const DNS_ERROR_RE = /\benotfound\b|\beai_again\b|\bgetaddrinfo\b|\bno such host const INTERRUPTED_NETWORK_ERROR_RE = /\beconnrefused\b|\beconnreset\b|\beconnaborted\b|\benetreset\b|\behostunreach\b|\behostdown\b|\benetunreach\b|\bepipe\b|\bsocket hang up\b|\bconnection refused\b|\bconnection reset\b|\bconnection aborted\b|\bnetwork is unreachable\b|\bhost is unreachable\b|\bfetch failed\b|\bconnection error\b|\bnetwork request failed\b/i; const REPLAY_INVALID_RE = - /\bprevious_response_id\b.*\b(?:invalid|unknown|not found|does not exist|expired|mismatch)\b|\btool_(?:use|call)\.(?:input|arguments)\b.*\b(?:missing|required)\b|\bincorrect role information\b|\broles must alternate\b/i; + /\bprevious_response_id\b.*\b(?:invalid|unknown|not found|does not exist|expired|mismatch)\b|\btool_(?:use|call)\.(?:input|arguments)\b.*\b(?:missing|required)\b|\bincorrect role information\b|\broles must alternate\b|\binput item id does not belong to this connection\b/i; const SANDBOX_BLOCKED_RE = /\bapproval is required\b|\bapproval timed out\b|\bapproval was denied\b|\bblocked by sandbox\b|\bsandbox\b.*\b(?:blocked|denied|forbidden|disabled|not allowed)\b/i;