Skip to content

Commit 5438f8a

Browse files
committed
fix(codex): clear stale context-engine projection after overflow retry
When a resumed Codex thread with a thread_bootstrap context-engine binding overflows during turn/start, the retry creates a fresh thread but preserved the old projection epoch in the saved binding. The fresh thread never received the projected context, creating a false binding invariant that makes future runs skip re-projection. After restartContextEngineCodexThread() creates the fresh retry thread, read the saved binding, detect the stale projection, clear it, and log the previous epoch for observability. Closes #88355 Signed-off-by: Sebastien Tardif <[email protected]>
1 parent 63621ee commit 5438f8a

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

extensions/codex/src/app-server/run-attempt.context-engine.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1233,7 +1233,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
12331233
const savedBinding = await readCodexAppServerBinding(sessionFile);
12341234
expect(savedBinding?.threadId).toBe("thread-fresh");
12351235
expect(savedBinding?.contextEngine?.engineId).toBe("lossless-claw");
1236-
expect(savedBinding?.contextEngine?.projection?.epoch).toBe("epoch-before");
1236+
expect(savedBinding?.contextEngine?.projection).toBeUndefined();
12371237
});
12381238

12391239
it("preserves a newer context-engine binding when a stale resumed thread overflows", async () => {

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,6 +1873,38 @@ export async function runCodexAppServerAttempt(
18731873
);
18741874
} else {
18751875
thread = await restartContextEngineCodexThread();
1876+
// The fresh retry thread was not bootstrapped with the
1877+
// context-engine projection. Clear the stale projection from
1878+
// the saved binding so the next run will re-project instead
1879+
// of assuming the old epoch is still in the thread.
1880+
{
1881+
const retryBinding = await readCodexAppServerBinding(activeSessionFile);
1882+
if (
1883+
retryBinding &&
1884+
retryBinding.threadId === thread.threadId &&
1885+
retryBinding.contextEngine?.projection
1886+
) {
1887+
const {
1888+
schemaVersion: _schemaVersion,
1889+
sessionFile: _boundSessionFile,
1890+
updatedAt: _updatedAt,
1891+
...bindingForWrite
1892+
} = retryBinding;
1893+
await writeCodexAppServerBinding(activeSessionFile, {
1894+
...bindingForWrite,
1895+
contextEngine: bindingForWrite.contextEngine
1896+
? { ...bindingForWrite.contextEngine, projection: undefined }
1897+
: undefined,
1898+
});
1899+
embeddedAgentLog.info(
1900+
"codex app-server cleared stale context-engine projection after overflow retry",
1901+
{
1902+
threadId: thread.threadId,
1903+
previousEpoch: retryBinding.contextEngine.projection.epoch,
1904+
},
1905+
);
1906+
}
1907+
}
18761908
emitCodexAppServerEvent(params, {
18771909
stream: "codex_app_server.lifecycle",
18781910
data: { phase: "thread_ready_retry", threadId: thread.threadId },

0 commit comments

Comments
 (0)