You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
During the PR #88262 audit, I found a separate active-context-engine risk: when a resumed Codex thread with a thread_bootstrap context-engine binding overflows during turn/start, OpenClaw retries on a fresh Codex thread but sends only the bare current prompt. The old context-engine projection binding is then preserved on the fresh thread even though the fresh thread never received the projected context in its turn/start input.
This one is not primarily caused by #88262. It is a pre-existing or adjacent behavior that matters for lossless-claw style integrations because it can silently turn a context-engine-owned run into a contextless fresh Codex thread.
sequenceDiagram
participant Engine as Context engine / lossless-claw
participant OC as OpenClaw Codex bridge
participant Old as Codex thread-old
participant Fresh as Codex thread-fresh
Engine->>OC: assemble prior context, projection epoch-before
OC->>Old: thread/resume existing bootstrapped thread
OC->>Old: turn/start current prompt
Old-->>OC: context window overflow
OC->>OC: clear old binding and restart thread
OC->>Fresh: thread/start with context-engine binding metadata
OC->>Fresh: turn/start "hello"
Note over Fresh: No "OpenClaw assembled context" in retry input
Fresh-->>OC: answer
OC->>OC: save binding for thread-fresh with projection epoch-before
Loading
Expected behavior
If the active context engine owns the session continuity and OpenClaw has to abandon a previously bootstrapped Codex thread, the fresh thread must not be marked as having the old projection unless it actually receives equivalent context.
The retry should do one of these:
reproject a smaller/bounded context-engine assembly into the fresh thread,
ask the context engine for an overflow-safe retry projection,
compact or ask Codex-native compaction only in a way that preserves the engine's continuity contract,
clear the projection binding on the fresh thread and fail loudly if context cannot be carried,
or store metadata that says the fresh thread started without the old context so the next run reprojects.
Actual behavior
The retry turn/start receives exactly the current prompt ("hello" in the test), while the resulting binding still records the old thread_bootstrap projection epoch.
That creates a false binding invariant: future runs can treat thread-fresh as if it already contains the context-engine bootstrap, but the retry turn that created it did not include the context projection in the user input.
Minimal reproduction shape
The current test is already a minimal reproduction:
Use an active context engine with engineId: "lossless-claw" and contextProjection: { mode: "thread_bootstrap", epoch: "epoch-before" }.
Start from an existing binding for thread-old with the same projection epoch.
Make thread/resume succeed.
Make turn/start on thread-old throw a context-window overflow.
Make thread/start produce thread-fresh.
Make turn/start on thread-fresh succeed.
Observe that the retry input is the bare current prompt and that the saved binding still has projection.epoch === "epoch-before".
Impact
This is a direct lossless-claw / active-context-engine continuity risk.
The failure mode is particularly subtle:
the user gets a successful answer,
the binding is updated to the fresh thread,
the binding still says the context-engine projection exists,
but the fresh thread never saw the old projected context.
Future runs may then skip reprojecting because the binding appears to match the context-engine epoch. That can make the loss of context persistent across turns.
PR #88262 changed the mirrored-history injection behavior. This issue is about a context-engine overflow recovery path that already has an active context engine.
It is still worth filing from the same audit because it is the one active lossless-claw path that did smell off: the normal active context-engine path appears to pass historyMessages into assembly and project context correctly, but this overflow retry path can create a fresh native thread without carrying context while retaining a projection binding.
Suggested fix direction
Do not save or retain a thread_bootstrap projection binding for a fresh retry unless the fresh retry actually bootstrapped that context.
One possible shape:
flowchart TD
A["turn/start overflow on resumed context-engine thread"] --> B["Need fresh Codex thread"]
B --> C{"Can project overflow-safe context?"}
C -->|Yes| D["Reproject bounded context into fresh turn/start"]
D --> E["Save binding with projection epoch"]
C -->|No| F["Start fresh without projection"]
F --> G["Do not mark binding as projected"]
G --> H["Fail loudly or force reproject on next run"]
Loading
At minimum, add a regression assertion that savedBinding.contextEngine.projection is absent or marked stale when the retry input does not contain the context-engine projection.
Evidence checked
Merged main commit inspected: 530351e394a19b1dd2943cb08259657a13f90572
Local PR-head audit checkout: 04a4427d0c0b8f2e7bb666bbbcda5c557d033972
Bug type
Behavior bug / lossless context-engine continuity risk.
Summary
During the PR #88262 audit, I found a separate active-context-engine risk: when a resumed Codex thread with a
thread_bootstrapcontext-engine binding overflows duringturn/start, OpenClaw retries on a fresh Codex thread but sends only the bare current prompt. The old context-engine projection binding is then preserved on the fresh thread even though the fresh thread never received the projected context in itsturn/startinput.This one is not primarily caused by #88262. It is a pre-existing or adjacent behavior that matters for lossless-claw style integrations because it can silently turn a context-engine-owned run into a contextless fresh Codex thread.
Why this is high confidence
run-attempt.context-engine.test.ts#L1137-L1237"pre-compaction context",engineId: "lossless-claw"andprojection.mode: "thread_bootstrap",thread/resumesucceeds forthread-old,turn/startonthread-oldthrowsCodex ran out of room in the model's context window,thread-fresh,turn/startonthread-freshsucceeds,compactis not called,assemblewas called only once,"hello",thread-freshwhile retaining the oldepoch-beforeprojection.run-attempt.ts#L1703-L1720restartContextEngineCodexThread()and starts the turn again:run-attempt.ts#L1721-L1749restartContextEngineCodexThreadis the same lifecycle start helper captured at startup:attempt-startup.ts#L249-L269attempt-startup.ts#L303-L305Diagram
sequenceDiagram participant Engine as Context engine / lossless-claw participant OC as OpenClaw Codex bridge participant Old as Codex thread-old participant Fresh as Codex thread-fresh Engine->>OC: assemble prior context, projection epoch-before OC->>Old: thread/resume existing bootstrapped thread OC->>Old: turn/start current prompt Old-->>OC: context window overflow OC->>OC: clear old binding and restart thread OC->>Fresh: thread/start with context-engine binding metadata OC->>Fresh: turn/start "hello" Note over Fresh: No "OpenClaw assembled context" in retry input Fresh-->>OC: answer OC->>OC: save binding for thread-fresh with projection epoch-beforeExpected behavior
If the active context engine owns the session continuity and OpenClaw has to abandon a previously bootstrapped Codex thread, the fresh thread must not be marked as having the old projection unless it actually receives equivalent context.
The retry should do one of these:
Actual behavior
The retry
turn/startreceives exactly the current prompt ("hello"in the test), while the resulting binding still records the oldthread_bootstrapprojection epoch.That creates a false binding invariant: future runs can treat
thread-freshas if it already contains the context-engine bootstrap, but the retry turn that created it did not include the context projection in the user input.Minimal reproduction shape
The current test is already a minimal reproduction:
engineId: "lossless-claw"andcontextProjection: { mode: "thread_bootstrap", epoch: "epoch-before" }.thread-oldwith the same projection epoch.thread/resumesucceed.turn/startonthread-oldthrow a context-window overflow.thread/startproducethread-fresh.turn/startonthread-freshsucceed.projection.epoch === "epoch-before".Impact
This is a direct lossless-claw / active-context-engine continuity risk.
The failure mode is particularly subtle:
Future runs may then skip reprojecting because the binding appears to match the context-engine epoch. That can make the loss of context persistent across turns.
Why this is separate from PR #88262
PR #88262 changed the mirrored-history injection behavior. This issue is about a context-engine overflow recovery path that already has an active context engine.
It is still worth filing from the same audit because it is the one active lossless-claw path that did smell off: the normal active context-engine path appears to pass
historyMessagesinto assembly and project context correctly, but this overflow retry path can create a fresh native thread without carrying context while retaining a projection binding.Suggested fix direction
Do not save or retain a
thread_bootstrapprojection binding for a fresh retry unless the fresh retry actually bootstrapped that context.One possible shape:
flowchart TD A["turn/start overflow on resumed context-engine thread"] --> B["Need fresh Codex thread"] B --> C{"Can project overflow-safe context?"} C -->|Yes| D["Reproject bounded context into fresh turn/start"] D --> E["Save binding with projection epoch"] C -->|No| F["Start fresh without projection"] F --> G["Do not mark binding as projected"] G --> H["Fail loudly or force reproject on next run"]At minimum, add a regression assertion that
savedBinding.contextEngine.projectionis absent or marked stale when the retry input does not contain the context-engine projection.Evidence checked
530351e394a19b1dd2943cb08259657a13f9057204a4427d0c0b8f2e7bb666bbbcda5c557d033972extensions/codex/src/app-server/run-attempt.context-engine.test.ts