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
- 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.
- Send a turn while the desktop app is busy enough not to answer
attestation/generate within
100 ms.
- 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.
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_TIMEOUTincodex-rs/app-server/src/attestation.rsis 100 ms. That is atight 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):
Two observations that may help.
The desktop app does answer — just after the deadline. Every timeout is immediately followed by
with
Nincrementing 1..6 across the six retries. The response arrives afteroutgoing.cancel_request(&request_id)has already removed the pending callback, so this is adeadline 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 othertransport, 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.rslogsDuration::from_millis(100).as_secs()is0, so every occurrence readstimeout_seconds=0. Thatreads 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 firstlog line.
What steps can reproduce the bug?
Thread id:
019f96f4-d4e8-7751-87c9-beba24bb3330Model:
gpt-5.6-sol, reasoning effort mediumRollout: 18,883 items / ~152 MB
run_pre_sampling_compact. Here that was roughly 8% ofturns — 28 pre-turn compactions across 338 turns on 2026-07-25 — not every turn.
attestation/generatewithin100 ms.
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=PreTurncompactions over the previous threedays, 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:
ATTESTATION_GENERATE_TIMEOUT, or make it configurable, so a busy desktop host is tolerated.all fail the same way, so they add latency without adding a chance of success.
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 thanas_secs(), so a sub-second timeout isnot 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 = falseon the model providermoves 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.