Repository navigation
IDE: resumed compacted thread loses exec/code-mode tools on Responses WebSocket #35751
Description
What version of the IDE extension are you using?
openai.chatgpt 26.721.41059, bundled codex-cli 0.146.0-alpha.3.1.
What subscription do you have?
Not captured during diagnostics.
Which IDE are you using?
VS Code Remote.
What platform is your computer?
Linux 5.10.0-60.18.0.50.oe2203.x86_64 x86_64 x86_64
What issue are you seeing?
An older IDE thread containing a compacted record with replacement_history resumes successfully, but the first turn after reopening has no usable terminal/file-edit tool surface. The assistant reports that no terminal execution tool is available.
The same workspace and permission profile work in a fresh thread. Forking the affected thread reproduces the failure. A clean, unmodified OpenAI extension also reproduces it, so no Codex Local Groups patch is required.
The VS Code Remote reconnect and Codex runtime resume both succeed. Sanitized Extension Host logs show:
maybe_resume_success
requestPermissionProfile=:danger-full-access
requestRuntimeWorkspaceRootCount=1
This rules out a missing workspace root, denied permission profile, or failed Extension Host resume as the immediate cause.
What steps can reproduce the bug?
- Use a long VS Code Codex thread until its rollout contains
compacted.payload.replacement_history. - Close/reopen VS Code, or navigate away and reopen the old thread.
- Ask the resumed thread to run
printf "TOOL_OK\n". - Observe that it reports no terminal execution tool is available and emits no command execution.
- Create a fresh thread in the same workspace and repeat. The command executes.
- Fork the affected thread and repeat. The fork remains affected.
Sanitized transport A/B
The same stored compacted history was replayed with request-shape instrumentation:
Responses WebSocket
startup prewarm response.create: generate=false, top-level tools_count=0
actual response.create: top-level tools_count=0
result: no command execution
Responses HTTP POST
same stored history
result: command execution emitted
output: TOOL_OK
Fresh thread over Responses WebSocket
result: command execution emitted
output: TOOL_OK
The WebSocket connection itself is healthy. The failure is specific to resumed compacted history on the WebSocket path.
For Responses Lite models, code-mode tools may be represented through additional_tools, so the decisive symptom is the missing usable tool surface. This report does not assume top-level tools must always be non-empty. The likely boundary is that resume/prewarm does not preserve or rebuild the current step's effective tool manifest.
What is the expected behavior?
Resuming compacted history should rebuild the current runtime tool surface independently of stored conversation items. Responses WebSocket prewarm and the actual turn should expose the same usable code-mode tools as HTTP and a fresh thread.
Additional information
Suggested regression test:
- Create a thread with compacted replacement history.
- Resume it through app-server.
- Use Responses WebSocket startup prewarm.
- Run a turn that calls public
execand thenexec_command. - Assert command execution succeeds.
- Compare the effective tool/
additional_toolsstate with the HTTP request for the same stored history.
Related but distinct reports:
- Post-compaction turn silently loses tool_search, app connectors, and configured MCP tools #34719: post-compaction deferred app/MCP tools disappear, but shell/core tools remain.
- gpt-5.6 Responses Lite turns do not expose exec/code-mode tools in codex exec #31894: fresh
codex execwith Responses Lite does not expose code-mode tools; this report is resume/compaction and WebSocket-specific, with fresh WebSocket and same-history HTTP controls succeeding. - Android Remote reconnect removes Notion/codex_apps tools from resumed chat; full Codex restart restores them #35298: Remote reconnect removes app connector tools; this report loses native exec/file-edit tools and is tied to compacted-history WebSocket replay.
- Older resumed Codex Desktop threads can miss newly available tools and may stay on legacy subagent runtime #25990: older threads miss newly introduced dynamic tools; this report loses tools that remain available in a fresh thread on the same runtime.
Raw rollout files, provider configuration, and transcripts are private. Sanitized request-shape traces can be provided.
Activity
antmanler commentedon Aug 18, 2026
Independently reproduced this and traced it to a specific mechanism in the Responses-over-WebSocket incremental request path. Your transport A/B (WS fails on resumed compacted history, HTTP succeeds on the same history, fresh thread over WS succeeds) matches our matrix exactly, and the mechanism below explains every cell — including two more we observed: a warm post-compaction turn succeeds, while
thread/forkof a compacted thread fails even in the same process.TL;DR: the tool table is transported as
input[0](spliced into the front of the request input), and the first turn after a fresh-process resume is sent as a prefix-based delta chained on the client-synthesized prewarm response. The tool table is therefore always the first casualty of the delta. When that delta contains acompactionitem — a context-replacement directive — the upstream rebuilds context from the delta alone, with zero tool declarations (noadditional_toolsinput item, no top-leveltoolsfield). No error frame is emitted; the failure is entirely silent.Affected versions
rust-v0.147.0(confirmed, deterministic 4/4 via app-server JSON-RPC)rust-v0.148.0-alpha.9(confirmed, same behaviour)Reproduced against a Responses API-compatible proxy. Requires an upstream that rebuilds context from a
compactioninput item rather than extending the chained prewarm state; it is not observable against an upstream that persists prewarm response state and treats the delta as a pure extension.Repro (app-server JSON-RPC, no IDE needed)
thread/startwithdynamicTools(flat form andtype: "namespace"form both reproduce).thread/compact/start.codex app-serverprocess.thread/resumein a fresh process (threadIdonly — restoration fromsession_metais expected and does work; the loss is not in rollout reconstruction).Expected: the tool is callable, as it is on a warm post-compaction turn.
Actual: the model reports having no tools (including
exec) and emits no tool calls. The outbound WebSocket frame carries no tool declarations.A resume/fork of a thread with no compaction in its rollout does not reproduce.
Mechanism (line references at
rust-v0.147.0)codex-rs/core/src/client.rs:884:codex-rs/core/src/client.rs:1188-1226:codex-rs/core/src/client.rs:1651-1660::1771sendswarmup = true, stored aslast_requestat:1685; the first real turn follows at:1830).The prewarm input is exactly
[additional_tools, developer_instructions], soprevious_items_len == 2and the first real turn's delta isinput[2..]— the entire conversation minus the tool table.Observed frames (RUST_LOG=trace, tungstenite)
Prewarm (
generate: false), identical in working and failing runs:Warm post-compaction turn — prefix comparison fails (last request was the compaction trigger), so a full request is sent and the tool call succeeds:
First turn after a fresh-process resume — delta path engages and the tool call fails:
The failing frame is byte-for-byte the full input with
[additional_tools, developer_instructions]removed. Top-level keys on that frame:client_metadata, include, input, model, parallel_tool_calls, previous_response_id, prompt_cache_key, reasoning, service_tier, store, stream, text, tool_choice, type— zero tool declarations anywhere.Why each matrix cell behaves the way it does:
compaction?Design claim
The tool table should not live inside a truncatable history prefix. Either:
ResponseItem::AdditionalTools, orcompactionitem, since such a request is a context rebuild rather than an extension.Workaround
For custom providers, disable the WebSocket transport:
responses_websocket_enabled()(codex-rs/core/src/client.rs:949-957) then returns false and every request goes over HTTP, whereResponsesApiRequesthas noprevious_response_idfield at all (:918-934) and the tool table is rebuilt per request (:862-891). Confirmed to make the full repro matrix pass — and notably, already-broken compacted threads heal immediately on the next turn, no thread recreation needed: rollout restoration is intact (the tools are in the session config), only the WS turn frame omits them.XhinLiang commentedon Sep 2, 2026
Additional independent reproduction on stable
codex-cli 0.151.0(sanitized).Environment
codex app-serverJSON-RPC over stdio, with one app-server process per loaded threadgpt-5.6-sol, reasoning effortmediumapprovalPolicy=never,sandbox=danger-full-accessshell_toolandunified_execwere enabledReproduction observed in three independent long-running threads
The same sequence occurred in all three threads:
contextCompactionitem and a persistedcompactedrecord withreplacement_historyappeared.execcalls continued to succeed later in that same live turn, so the tool surface was still healthy immediately after compaction.thread/resumereturned success with the same model, cwd, approval, and sandbox overrides.commandExecutionorcustom_tool_call. The model either reported that no command executor was available or rendered intended calls such asto=tools.exec_commandas ordinary assistant text.One thread provided a tight sequence:
Impact
One affected automation thread accumulated 47 completed retry runs and 5 failed retry runs after the transition, all without a structured tool call. The host treated the successful
thread/resumeas healthy, so its existing fresh-thread recovery path was never activated.This matches the mechanism already documented in this issue and extends the confirmed affected range from the earlier 0.147/0.148 builds to stable
0.151.0. It also reproduces outside the IDE UI through a third-party app-server stdio integration.I did not capture an upstream Responses HTTP versus WebSocket A/B, so I cannot independently confirm the transport-level mechanism. The compaction plus fresh-process resume boundary and the loss of native exec are deterministic across the three observed threads.
I have not yet replayed the fixture on
0.152.1.