Summary
When Codex previous-response continuation is enabled, its local state is keyed only by x-claude-code-session-id.
Anthropic Messages ingress does not retain x-claude-code-agent-id or x-claude-code-parent-agent-id, and RequestContext has no Agent identity. Different Agents sharing one Claude session therefore compete for the same continuation candidate, current-turn state, and continuation-enabled WebSocket pool.
Divergent Agent histories consume or replace one another's continuation state and fall back to full input. Compatible histories may instead select a response ID published by another Agent.
Affected snapshot:
main@536e60640ffc83c392223edbcdc4f6a9449ff875
Why this occurs with Claude Code SubAgents
This is not an artificial session-ID collision.
Claude Code can run multiple SubAgents under one parent session. In that topology, child Agents share the Claude session ID while carrying distinct Agent IDs:
- Agent A:
session=S, agent=A
- Agent B:
session=S, agent=B
Because the proxy retains only session=S, both Agents address the same continuation namespace.
This request topology was observed in the Claude Code 2.1.220 client implementation. A live header capture would still be useful to confirm it on the wire.
Minimum reproduction
Use an instrumented Codex WebSocket upstream that records outbound input and previous_response_id.
- Enable previous-response continuation and use WebSocket transport.
- Send Agent A1 with
session=S, agent=A, and let it publish a continuation response ID.
- Send Agent B1 with
session=S, agent=B, but a divergent history.
- Observe that B consumes the single continuation candidate for
S and falls back to full input.
- Resume Agent A with A2.
- Observe that A1's continuation is no longer independently available to A.
- Repeat with distinct session IDs as a control; A's continuation remains available.
If B instead has the same non-input signature, a strict structural prefix matching A's saved input, and a non-empty suffix, B can select A's stored response ID. Exact transcript equality produces empty_delta and is not this case.
Sequential interleaving is sufficient; concurrency is not required.
Expected behavior
Continuation and reusable WebSocket state belonging to one Agent should not be consumed, replaced, or selected by another Agent merely because both share a Claude session.
When no stable Agent identity is available, continuation should use an explicit safe fallback.
Actual behavior
- Ingress reads
x-claude-code-session-id, but not Agent identity headers (src/server.rs:1401-1405).
RequestContext contains session_id but no Agent identity (src/provider.rs:106-114).
ContinuationRegistry has one entry per session. Lookup removes or replaces that entry, and only the shared current turn may publish (src/providers/codex/continuation.rs:21-33,77-102,208-224).
- Divergent histories fall back after shared-state lookup; compatible prefixes may select the shared response ID (
src/providers/codex/continuation.rs:129-166,313-375).
previous_response_id and its delta are emitted on WebSocket, not HTTP (src/providers/codex/client.rs:306-330,983-1000).
- The continuation-enabled WebSocket pool is also keyed only by
ctx.session_id (src/providers/codex/client.rs:2051-2060; src/providers/codex/websocket.rs:223-246).
Impact
The directly demonstrated effects are:
- cross-Agent continuation consumption and replacement;
- loss of per-Agent incremental-context reuse;
- full-input fallback for divergent histories;
- conditional selection of another Agent's opaque response lineage.
This report does not claim measured production frequency, latency, token impact, semantic corruption, or a security boundary violation.
Previous-response continuation is disabled by default. The wire-level response-ID reuse described above applies to WebSocket transport.
Server compaction also has session-keyed state, but it has additional attempt, model, phase, and summary-anchor guards. It is related but not required to reproduce this issue.
Proposed follow-up
I am preparing a related PR that is intended to address this by:
- retaining a stable Agent discriminator at ingress and carrying it in
RequestContext;
- using an explicit owner key for continuation, current-turn publication, and WebSocket pooling;
- defining safe fallback behavior when Agent identity is unavailable;
- adding A/B regression tests using one session ID and distinct Agent IDs;
- covering divergent histories, compatible prefixes, completion ordering, and a distinct-session control.
I will link the PR here once it is ready.
Summary
When Codex previous-response continuation is enabled, its local state is keyed only by
x-claude-code-session-id.Anthropic Messages ingress does not retain
x-claude-code-agent-idorx-claude-code-parent-agent-id, andRequestContexthas no Agent identity. Different Agents sharing one Claude session therefore compete for the same continuation candidate, current-turn state, and continuation-enabled WebSocket pool.Divergent Agent histories consume or replace one another's continuation state and fall back to full input. Compatible histories may instead select a response ID published by another Agent.
Affected snapshot:
main@536e60640ffc83c392223edbcdc4f6a9449ff875Why this occurs with Claude Code SubAgents
This is not an artificial session-ID collision.
Claude Code can run multiple SubAgents under one parent session. In that topology, child Agents share the Claude session ID while carrying distinct Agent IDs:
session=S,agent=Asession=S,agent=BBecause the proxy retains only
session=S, both Agents address the same continuation namespace.This request topology was observed in the Claude Code 2.1.220 client implementation. A live header capture would still be useful to confirm it on the wire.
Minimum reproduction
Use an instrumented Codex WebSocket upstream that records outbound input and
previous_response_id.session=S,agent=A, and let it publish a continuation response ID.session=S,agent=B, but a divergent history.Sand falls back to full input.If B instead has the same non-input signature, a strict structural prefix matching A's saved input, and a non-empty suffix, B can select A's stored response ID. Exact transcript equality produces
empty_deltaand is not this case.Sequential interleaving is sufficient; concurrency is not required.
Expected behavior
Continuation and reusable WebSocket state belonging to one Agent should not be consumed, replaced, or selected by another Agent merely because both share a Claude session.
When no stable Agent identity is available, continuation should use an explicit safe fallback.
Actual behavior
x-claude-code-session-id, but not Agent identity headers (src/server.rs:1401-1405).RequestContextcontainssession_idbut no Agent identity (src/provider.rs:106-114).ContinuationRegistryhas one entry per session. Lookup removes or replaces that entry, and only the shared current turn may publish (src/providers/codex/continuation.rs:21-33,77-102,208-224).src/providers/codex/continuation.rs:129-166,313-375).previous_response_idand its delta are emitted on WebSocket, not HTTP (src/providers/codex/client.rs:306-330,983-1000).ctx.session_id(src/providers/codex/client.rs:2051-2060;src/providers/codex/websocket.rs:223-246).Impact
The directly demonstrated effects are:
This report does not claim measured production frequency, latency, token impact, semantic corruption, or a security boundary violation.
Previous-response continuation is disabled by default. The wire-level response-ID reuse described above applies to WebSocket transport.
Server compaction also has session-keyed state, but it has additional attempt, model, phase, and summary-anchor guards. It is related but not required to reproduce this issue.
Proposed follow-up
I am preparing a related PR that is intended to address this by:
RequestContext;I will link the PR here once it is ready.