From 4f340b8812ded7b23e238dd921fad048afba189a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 23 Feb 2026 10:38:49 -0500 Subject: [PATCH] fix(agents): avoid classifying reasoning-required errors as context overflow (#24593) * Agents: exclude reasoning-required errors from overflow detection * Tests: cover reasoning-required overflow classification guard * Tests: format reasoning-required endpoint errors --- ...d-helpers.formatassistanterrortext.test.ts | 9 ++++++ ...dded-helpers.isbillingerrormessage.test.ts | 22 +++++++++++++++ src/agents/pi-embedded-helpers/errors.ts | 28 +++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts b/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts index 3aedccefe..397445067 100644 --- a/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts +++ b/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts @@ -35,6 +35,15 @@ describe("formatAssistantErrorText", () => { ); expect(formatAssistantErrorText(msg)).toContain("Context overflow"); }); + it("returns a reasoning-required message for mandatory reasoning endpoint errors", () => { + const msg = makeAssistantError( + "400 Reasoning is mandatory for this endpoint and cannot be disabled.", + ); + const result = formatAssistantErrorText(msg); + expect(result).toContain("Reasoning is required"); + expect(result).toContain("/think minimal"); + expect(result).not.toContain("Context overflow"); + }); it("returns a friendly message for Anthropic role ordering", () => { const msg = makeAssistantError('messages: roles must alternate between "user" and "assistant"'); expect(formatAssistantErrorText(msg)).toContain("Message ordering conflict"); diff --git a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts index 5900ec5df..8b4b23ac6 100644 --- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts +++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts @@ -208,6 +208,17 @@ describe("isContextOverflowError", () => { expect(isContextOverflowError("We're debugging context overflow issues")).toBe(false); expect(isContextOverflowError("Something is causing context overflow messages")).toBe(false); }); + + it("excludes reasoning-required invalid-request errors", () => { + const samples = [ + "400 Reasoning is mandatory for this endpoint and cannot be disabled.", + '{"type":"error","error":{"type":"invalid_request_error","message":"Reasoning is mandatory for this endpoint and cannot be disabled."}}', + "This model requires reasoning to be enabled", + ]; + for (const sample of samples) { + expect(isContextOverflowError(sample)).toBe(false); + } + }); }); describe("error classifiers", () => { @@ -286,6 +297,17 @@ describe("isLikelyContextOverflowError", () => { expect(isLikelyContextOverflowError(sample)).toBe(false); } }); + + it("excludes reasoning-required invalid-request errors", () => { + const samples = [ + "400 Reasoning is mandatory for this endpoint and cannot be disabled.", + '{"type":"error","error":{"type":"invalid_request_error","message":"Reasoning is mandatory for this endpoint and cannot be disabled."}}', + "This endpoint requires reasoning", + ]; + for (const sample of samples) { + expect(isLikelyContextOverflowError(sample)).toBe(false); + } + }); }); describe("isTransientHttpError", () => { diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index 6a40f1d7b..b4ccfa943 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -34,6 +34,19 @@ function formatRateLimitOrOverloadedErrorCopy(raw: string): string | undefined { return undefined; } +function isReasoningConstraintErrorMessage(raw: string): boolean { + if (!raw) { + return false; + } + const lower = raw.toLowerCase(); + return ( + lower.includes("reasoning is mandatory") || + lower.includes("reasoning is required") || + lower.includes("requires reasoning") || + (lower.includes("reasoning") && lower.includes("cannot be disabled")) + ); +} + export function isContextOverflowError(errorMessage?: string): boolean { if (!errorMessage) { return false; @@ -45,6 +58,10 @@ export function isContextOverflowError(errorMessage?: string): boolean { return false; } + if (isReasoningConstraintErrorMessage(errorMessage)) { + return false; + } + const hasRequestSizeExceeds = lower.includes("request size exceeds"); const hasContextWindow = lower.includes("context window") || @@ -85,6 +102,10 @@ export function isLikelyContextOverflowError(errorMessage?: string): boolean { return false; } + if (isReasoningConstraintErrorMessage(errorMessage)) { + return false; + } + if (CONTEXT_WINDOW_TOO_SMALL_RE.test(errorMessage)) { return false; } @@ -464,6 +485,13 @@ export function formatAssistantErrorText( ); } + if (isReasoningConstraintErrorMessage(raw)) { + return ( + "Reasoning is required for this model endpoint. " + + "Use /think minimal (or any non-off level) and try again." + ); + } + // Catch role ordering errors - including JSON-wrapped and "400" prefix variants if ( /incorrect role information|roles must alternate|400.*role|"message".*role.*information/i.test(