Skip to content

Codex App Bug — attestation generation times out at 100 ms, blocking pre-turn compaction #35445

Description

@aididhaiqal

What version of the Codex App are you using (From "About Codex" dialog)?

26.721.41059

What subscription do you have?

Pro 20x

What platform is your computer?

Darwin 27.0.0 arm64 arm

What issue are you seeing?

ATTESTATION_GENERATE_TIMEOUT in codex-rs/app-server/src/attestation.rs is 100 ms. That is a
tight budget for an IPC round-trip to the desktop app, and when the app does not answer within it
the attestation request is cancelled, the header falls back to
AppServerAttestationStatus::Timeout, and any turn that depends on it fails.

On a long-running thread, one such timeout during pre-turn compaction was enough to strand the
thread: every subsequent turn re-attempted the same compaction and hit the same timeout, so the
thread could not take another turn at all. It surfaced in the UI as IO error broken pipe.

The client log for each failure (thread/turn ids and model retained, no message content):

turn{... model=gpt-5.6-sol codex.turn.reasoning_effort=medium}
  :session_task.run:run_turn:run_pre_sampling_compact
  :run_auto_compact{reason=ContextLimit phase=PreTurn}
  :model_client.stream_responses_websocket{model=gpt-5.6-sol wire_api=responses
     transport="responses_websocket" api.path="responses" turn.has_metadata_header=true}
  :model_client.websocket_connection{provider=openai wire_api=responses}
  : attestation generation request timed out timeout_seconds=0

ERROR codex_core::session::turn ... run_turn: Failed to run pre-sampling compact

Two observations that may help.

The desktop app does answer — just after the deadline. Every timeout is immediately followed by

WARN codex_app_server::outgoing_message  could not find callback for Integer(N)

with N incrementing 1..6 across the six retries. The response arrives after
outgoing.cancel_request(&request_id) has already removed the pending callback, so this is a
deadline that is too short rather than a host that fails to respond.

It is specific to the WebSocket path. Attestation is only requested inside
websocket_connection: all 8 occurrences observed here were in that span, and none on any other
transport, across roughly 14.5k WebSocket turns per day. The same thread runs its turns without
incident over HTTP-SSE, which is consistent with #20619 attaching the header at websocket handshake
time. This is also why the failure presents on compaction — that is the path that opens a fresh
connection mid-session.

The log line under-reports the timeout. attestation.rs logs

warn!(
    timeout_seconds = timeout_duration.as_secs(),
    "attestation generation request timed out"
);

Duration::from_millis(100).as_secs() is 0, so every occurrence reads timeout_seconds=0. That
reads as a misconfigured zero rather than a 100 ms budget, and it cost several hours of misdiagnosis
here before the constant was found. as_millis() would have made the cause obvious from the first
log line.

What steps can reproduce the bug?

Thread id: 019f96f4-d4e8-7751-87c9-beba24bb3330
Model: gpt-5.6-sol, reasoning effort medium
Rollout: 18,883 items / ~152 MB

  1. Take a thread that occasionally needs run_pre_sampling_compact. Here that was roughly 8% of
    turns — 28 pre-turn compactions across 338 turns on 2026-07-25 — not every turn.
  2. Send a turn while the desktop app is busy enough not to answer attestation/generate within
    100 ms.
  3. The attestation request times out and the turn ends with Failed to run pre-sampling compact.

The failure is self-sustaining rather than transient. A compaction that fails leaves the thread
still over its limit, so the next turn needs one too and fails the same way. One unlucky 100 ms
window therefore takes the thread out permanently, not just for that turn. That is what makes this
worse than a normal flaky-IPC bug: there is no path back, because the operation that would recover
the thread is the operation that cannot run.

Frequency here: 8 occurrences between 2026-07-16 and 2026-07-26. Sporadic single events for the
first ten days, then one thread stuck for roughly 90 minutes, during which every attempted turn
failed. Nothing changed in the app version (unchanged since 2026-07-25 04:57), so the difference
appears to be how quickly the app answers, not a code change.

The same thread had completed 63 successful phase=PreTurn compactions over the previous three
days, so the operation is not inherently broken — it is timing-sensitive, and one loss is enough to
strand the thread.

What is the expected behavior?

A transient IPC delay should not make a thread permanently untakeable.

Concretely, any of:

  • Raise ATTESTATION_GENERATE_TIMEOUT, or make it configurable, so a busy desktop host is tolerated.
  • Retry with backoff rather than six attempts against the same 100 ms deadline. The current retries
    all fail the same way, so they add latency without adding a chance of success.
  • Treat an attestation timeout as non-fatal for compaction specifically. Failing the whole turn on a
    missing anti-abuse header is a strong response to a 100 ms IPC delay, and it leaves the thread
    with no path forward, since compaction is required for the next turn to proceed.

Separately and independently: log as_millis() rather than as_secs(), so a sub-second timeout is
not reported as 0.

Additional information

The attestation mechanism was introduced in #20619, which scopes it to "Responses / compaction /
realtime setup paths" — compaction is the path that fails here.

Workaround, for anyone who hits this: setting supports_websockets = false on the model provider
moves the session to HTTP-SSE, which does not request attestation, and the stranded compaction then
completes. Re-enabling WebSocket afterwards is fine — the thread is back under its limit by then.

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingcontextIssues related to context management (including compaction)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions