Skip to content

Commit 5cfa577

Browse files
Recover Codex context overflow prompt errors (#85542)
* fix: recover codex context overflow prompt errors * test: align Codex overflow prompt proof * test: satisfy manifest registry mock contract --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent d967760 commit 5cfa577

7 files changed

Lines changed: 62 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
1717

1818
- Agents/media: send direct fallback for generated media still missing after an active requester wake fails. (#85489) Thanks @fuller-stack-dev.
1919
- Agents: derive overflow compaction budgets from provider-reported and synthetic over-budget token counts so confirmed context overflows compact before retrying. (#70473) Thanks @fuller-stack-dev.
20+
- Agents/Codex: recover Codex context-window prompt errors through overflow compaction and surface reset guidance when recovery is exhausted. (#85542) Thanks @fuller-stack-dev.
2021
- Agent transcript: include OpenClaw agent session logs when finding local transcript candidates.
2122
- Sessions/doctor: load large session stores without clone amplification during read-only doctor checks and reclaim stale `sessions.json.*.tmp` sidecars. Fixes #56827. Thanks @openperf.
2223
- Tests: clean successful plugin gateway gauntlet isolated temp roots while keeping an explicit preservation switch for failed/debug runs.

src/agents/pi-embedded-helpers/errors.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
33
import type { OpenClawConfig } from "../../config/types.openclaw.js";
44
import { MALFORMED_STREAMING_FRAGMENT_ERROR_MESSAGE } from "../../shared/assistant-error-format.js";
55
import { makeAssistantMessageFixture } from "../test-helpers/assistant-message-fixtures.js";
6-
import { formatAssistantErrorText } from "./errors.js";
6+
import { formatAssistantErrorText, isLikelyContextOverflowError } from "./errors.js";
77

88
const { toolPolicyAuditInfo } = vi.hoisted(() => ({
99
toolPolicyAuditInfo: vi.fn(),
@@ -92,3 +92,13 @@ describe("formatAssistantErrorText streaming JSON parse classification", () => {
9292
);
9393
});
9494
});
95+
96+
describe("isLikelyContextOverflowError", () => {
97+
it("detects Codex promptError wording for a full context window", () => {
98+
expect(
99+
isLikelyContextOverflowError(
100+
"Codex ran out of room in the model's context window. Start a new thread or clear earlier history before retrying.",
101+
),
102+
).toBe(true);
103+
});
104+
});

src/agents/pi-embedded-helpers/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ export function isContextOverflowError(errorMessage?: string): boolean {
108108
lower.includes("context window") ||
109109
lower.includes("context length") ||
110110
lower.includes("maximum context length");
111+
const hasContextWindowOutOfRoom =
112+
hasContextWindow && (lower.includes("ran out of room") || lower.includes("ran out of space"));
111113
return (
112114
lower.includes("request_too_large") ||
113115
(lower.includes("invalid_argument") && lower.includes("maximum number of tokens")) ||
@@ -119,6 +121,7 @@ export function isContextOverflowError(errorMessage?: string): boolean {
119121
lower.includes("exceeds model context window") ||
120122
lower.includes("model token limit") ||
121123
(lower.includes("input exceeds") && lower.includes("maximum number of tokens")) ||
124+
hasContextWindowOutOfRoom ||
122125
(hasRequestSizeExceeds && hasContextWindow) ||
123126
lower.includes("context overflow:") ||
124127
lower.includes("exceed context limit") ||

src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export const mockedIsLikelyContextOverflowError = vi.fn((msg?: string) => {
182182
return (
183183
lower.includes("request_too_large") ||
184184
lower.includes("context window exceeded") ||
185+
(lower.includes("context window") && lower.includes("ran out of room")) ||
185186
lower.includes("prompt is too long")
186187
);
187188
});
@@ -360,6 +361,7 @@ export function resetRunOverflowCompactionHarnessMocks(): void {
360361
return (
361362
lower.includes("request_too_large") ||
362363
lower.includes("context window exceeded") ||
364+
(lower.includes("context window") && lower.includes("ran out of room")) ||
363365
lower.includes("prompt is too long")
364366
);
365367
});

src/agents/pi-embedded-runner/run.overflow-compaction.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
mockedExtractObservedOverflowTokenCount,
2424
mockedGlobalHookRunner,
2525
mockedGetApiKeyForModel,
26+
mockedIsLikelyContextOverflowError,
2627
mockedMarkAuthProfileSuccess,
2728
mockedPickFallbackThinkingLevel,
2829
mockedResolveAuthProfileOrder,
@@ -1543,6 +1544,39 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
15431544
expect(result.meta.error).toBeUndefined();
15441545
});
15451546

1547+
it("surfaces a visible blocked payload for Codex promptError overflow without assistant text", async () => {
1548+
const promptError = new Error(
1549+
"Codex ran out of room in the model's context window. Start a new thread or clear earlier history before retrying.",
1550+
);
1551+
const terminalLifecycleMeta: Array<Record<string, unknown>> = [];
1552+
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
1553+
makeAttemptResult({
1554+
promptError,
1555+
promptErrorSource: "prompt",
1556+
assistantTexts: [],
1557+
attemptUsage: { input: 0, output: 0, total: 0 },
1558+
setTerminalLifecycleMeta: (meta) => {
1559+
terminalLifecycleMeta.push(meta);
1560+
},
1561+
}),
1562+
);
1563+
1564+
const result = await runEmbeddedPiAgent(overflowBaseRunParams);
1565+
1566+
expect(mockedIsLikelyContextOverflowError).toHaveBeenCalledWith(promptError.message);
1567+
expect(mockedCompactDirect).toHaveBeenCalledTimes(1);
1568+
expect(result.payloads?.[0]).toMatchObject({
1569+
isError: true,
1570+
text: expect.stringContaining("Context overflow"),
1571+
});
1572+
expect(result.payloads?.[0]?.text).toContain("/reset");
1573+
expect(result.payloads?.[0]?.text).toContain("/new");
1574+
expect(result.meta.error?.kind).toBe("context_overflow");
1575+
expect(result.meta.livenessState).toBe("blocked");
1576+
expect(result.meta.finalAssistantVisibleText).toBe(result.payloads?.[0]?.text);
1577+
expect(terminalLifecycleMeta.at(-1)).toMatchObject({ livenessState: "blocked" });
1578+
});
1579+
15461580
it("does not reset compaction attempt budget after successful tool-result truncation", async () => {
15471581
const overflowError = queueOverflowAttemptWithOversizedToolOutput(
15481582
mockedRunEmbeddedAttempt,

src/agents/pi-embedded-runner/run.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,16 +2149,21 @@ export async function runEmbeddedPiAgent(
21492149
);
21502150
}
21512151
const kind = isCompactionFailure ? "compaction_failure" : "context_overflow";
2152+
const overflowRecoveryText =
2153+
"Context overflow: prompt too large for the model. " +
2154+
"Try /reset (or /new) to start a fresh session, or use a larger-context model.";
2155+
log.warn(
2156+
`[context-overflow-recovery] exhausted provider overflow recovery for ${provider}/${modelId}; ` +
2157+
`livenessState=blocked suggestedAction=reset_or_new kind=${kind}`,
2158+
);
21522159
attempt.setTerminalLifecycleMeta?.({
21532160
replayInvalid: resolveReplayInvalidForAttempt(),
21542161
livenessState: "blocked",
21552162
});
21562163
return {
21572164
payloads: [
21582165
{
2159-
text:
2160-
"Context overflow: prompt too large for the model. " +
2161-
"Try /reset (or /new) to start a fresh session, or use a larger-context model.",
2166+
text: overflowRecoveryText,
21622167
isError: true,
21632168
},
21642169
],
@@ -2176,6 +2181,8 @@ export async function runEmbeddedPiAgent(
21762181
lastTurnTotal,
21772182
}),
21782183
systemPromptReport: attempt.systemPromptReport,
2184+
finalAssistantVisibleText: overflowRecoveryText,
2185+
finalAssistantRawText: overflowRecoveryText,
21792186
finalPromptText: attempt.finalPromptText,
21802187
replayInvalid: resolveReplayInvalidForAttempt(),
21812188
livenessState: "blocked",

src/commands/doctor-legacy-config.migrations.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ vi.mock("../plugins/setup-registry.js", () => ({
1414

1515
vi.mock("../plugins/manifest-registry.js", () => ({
1616
loadPluginManifestRegistry: () => ({
17+
diagnostics: [],
1718
plugins: [
1819
{
1920
id: "brave",

0 commit comments

Comments
 (0)