Skip to content

Codex continuation state collides across agents sharing a Claude session #95

Description

@ItsAlbertZhang

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.

  1. Enable previous-response continuation and use WebSocket transport.
  2. Send Agent A1 with session=S, agent=A, and let it publish a continuation response ID.
  3. Send Agent B1 with session=S, agent=B, but a divergent history.
  4. Observe that B consumes the single continuation candidate for S and falls back to full input.
  5. Resume Agent A with A2.
  6. Observe that A1's continuation is no longer independently available to A.
  7. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions