Skip to content

[URGENT] Stable IDE extension ships a prerelease CLI with under-development "Code mode" silently active, breaking any long-running MCP calls and burning tokens while waiting #29122

Open

Description

@alexchexes

What version of the IDE extension are you using?

26.616.31447 (bundled codex-cli 0.142.0-alpha.1)

What subscription do you have?

Plus

Which IDE are you using?

VS Code

What platform is your computer?

Microsoft Windows NT 10.0.26100.0 x64

What issue are you seeing?

The just-released, non-prerelease VS Code extension 26.616.31447 bundles codex-cli 0.142.0-alpha.1 and routes MCP tools through code-mode exec cells even though I never enabled or opted into code mode. This silently changes the execution contract in four serious ways:

  • an under-development mode is activated without consent
  • the model now controls whether a call remains alive
  • merely waiting for long-running mcp call return consumes tokens and context
  • the only available mitigation depends on an undocumented Codex-generated namespace that MCP servers cannot control.

In detail:

1. Under-development code mode is activated without opt-in

The published Codex sample configuration describes code mode as "under development and off by default." At the time of the failure, my config did not contain [features.code_mode], the affected binary reported code_mode ... false in codex features list, and the installed IDE extension was a regular release rather than a prerelease. Nevertheless, the affected IDE thread exposed MCP tools only through code-mode exec.

The extension itself is not marked as a preview or prerelease, but it bundles a CLI whose codex-package.json version is 0.142.0-alpha.1; OpenAI's corresponding rust-v0.142.0-alpha.1 release is explicitly classified as a prerelease. The IDE does not offer a separate bundled-CLI channel selection, so this prerelease executable arrived through the regular extension without any user opt-in.

Codex PR #25031 indicates that model-catalog tool_mode metadata can select code_mode or code_mode_only and override local feature flags. If that mechanism caused this behavior, an under-development execution mode with materially different tool-lifetime and token semantics was activated remotely without user consent, a visible migration warning, or an obvious local control that takes precedence.

2. MCP call lifetime is now controlled by the model for all MCPs by default

Previously, a direct MCP call remained blocked in the runtime until the tool completed, the configured tool_timeout_sec expired, the transport failed, or the user cancelled it. The model had no decision point while the call was pending.

In code mode, exec yields while the nested MCP call is still healthy. After every yield, the model must choose whether to issue another wait or terminate: true. The MCP server cannot set yield_time_ms, cannot prevent termination, and cannot tell the code-mode runtime that a multi-hour duration is expected. The configured MCP timeout remains only an upper bound; it no longer gives the call an uninterrupted runtime wait up to that deadline. Correct call lifetime now depends on the model repeatedly making the right scheduling decision without enough information to do so reliably.

To be precise, tool_timeout_sec itself is still user-configured and host-enforced as a maximum; it is not selected by the model. What the model now controls is each code-mode yield_time_ms and the decision to call terminate: true. There is no MCP server option that binds those decisions to tool_timeout_sec or prevents an earlier model-authored termination.

3. Waiting is now a token-consuming model loop

The initial exec yield defaults to 10 seconds unless the model writes a different yield_time_ms pragma. Every subsequent wait interval is also selected by the model and independently defaults to 10 seconds. Each yield produces a model-visible tool output such as Script running with cell ID ..., triggers another model inference, and appends both the wait call and its output to the conversation history.

A 10-hour MCP call using the 10-second defaults can therefore create roughly 3,600 additional model turns and 3,600 persistent call/output pairs. The old direct-MCP path used no model turns or intermediate transcript tokens while the call was pending. The new path can consume token/rate limits, grow the context until compaction or exhaustion, and expose the healthy call to thousands of unnecessary model-authored termination decisions.

This looks designed for interactive orchestration, but long-running blocking tools were not handled safely.

4. The mitigation depends on an unstable, under-documented, internal mcp___ prefix / namespace.

The only deterministic mitigation currently available is features.code_mode.direct_only_tool_namespaces. It requires an exact model-visible namespace such as mcp__ask_human, but that is not a name published by the MCP server. Codex derives it from the client-side [mcp_servers.<name>] key, replaces unsupported characters, adds an internally generated mcp__ prefix, and then performs an exact string match. The MCP server cannot discover the resulting name, request direct execution, or declare that its calls are intentionally long-running.

This derivation is not defined in the MCP documentation as a stable contract. The official sample contains an example value but does not explain how users or server authors should determine it. The current Codex source calls mcp__ a historical prefix, and the affected CLI exposes a separate under-development non_prefixed_mcp_tool_names feature. A naming change could therefore silently stop the mitigation from matching.

Requiring every user to reverse-engineer and maintain a client-generated namespace is not a safe design for preserving tool-call lifetime. This must either remain runtime-controlled by the configured timeout and explicit user cancellation, or Codex needs a documented, discoverable, stable configuration contract and an MCP-level mechanism through which a server can declare the required execution behavior.

Concrete failure reproducer

The concrete reproducer was ask-human - a human-in-the-loop MCP that is intentionally designed to keep a tool call pending as many times as the user needs before providing the answer. It makes all four problems easy to observe, though none is specific to that tool: the same code-mode control path can affect any long-running MCP operation, such as a deployment, data import, remote job, or approval workflow.

In the example given, the MCP tool and Codex MCP client were both configured with explicit 86400-second timeouts. The user did not answer or cancel the prompt, and the MCP tool did not return a timeout or error.

After an intermediate 240-second wait yielded Script running with cell ID 6, the model issued a second wait call with terminate: true. This cancelled the still-valid MCP call and the agent then repeated the question as ordinary chat text.

The VS Code UI displayed Tool returned no content for the nested MCP call even though the rollout recorded that the exec cell was still running.

Sanitized rollout timeline (UTC):

14:35:45  exec starts tools.mcp__ask_human__ask_human(...)
14:35:55  exec yields: Script running with cell ID 6
14:35:58  wait({ cell_id: "6", yield_time_ms: 240000 })
14:39:58  wait yields: Script running with cell ID 6
14:40:07  wait({ cell_id: "6", terminate: true, ... })
14:40:07  Script terminated
14:40:19  agent ends turn and repeats the unanswered question

There is no mcp_tool_call_end event for the failed call. The explicit terminate: true call is model-authored in the rollout; it was not caused by a user action or MCP timeout.

What steps can reproduce the bug?

  1. Configure a stdio MCP server with any tool that legitimately runs longer than 4 minutes. A human-approval tool like this one is a convenient reproducer, but the operation can also be a long-running job with deterministic completion.

  2. Set tool_timeout_sec to a long value, for example:

    [mcp_servers.example]
    command = "..."
    tool_timeout_sec = 86400
  3. Start a new Codex IDE thread using extension 26.616.31447, bundled CLI 0.142.0-alpha.1, and GPT-5.5 with xhigh reasoning.

  4. Prompt Codex in a way so that it calls the long-running MCP tool. In this configuration Codex invokes it through code mode.

  5. Do not complete or cancel the MCP operation for at least 4 minutes.

Anyway, this appears to be model-dependent rather than a deterministic runtime timeout. In a second 0.142.0-alpha.1 session, answering within 58 seconds succeeded after two model-authored 30-second wait calls. That successful call still required extra model turns solely to keep waiting. The core regression is that every code-mode yield both consumes another inference and creates another decision point at which the model can terminate a healthy nested tool.

What is the expected behavior?

An under-development execution mode must not be silently enabled in a regular IDE release when the user has not opted in and the local feature state reports it as disabled. If remote model metadata can select code mode, that behavior and its changed tool-execution semantics need to be documented, visible, and subject to a reliable user-controlled override.

A safety-critical override must not depend on reverse-engineering an undocumented generated namespace. Codex should either preserve direct runtime-managed behavior automatically for pending MCP calls, provide a stable and discoverable client configuration keyed by the configured MCP server name, or support server-declared execution requirements through the protocol.

Codex must not terminate a nested MCP call solely because an exec/wait interval yielded while the call is still running. It should preserve the call until one of these occurs:

  • the nested tool completes;
  • the configured MCP timeout actually expires;
  • the user explicitly cancels; or
  • the transport reports a real failure.

Long-running nested tool calls should ideally remain runtime-managed rather than requiring repeated model decisions. At minimum, a running-cell yield should be unambiguously represented to both the model and UI as an intermediate state, not Tool returned no content.

No model inference should be required merely because a tool is still running, and runtime status updates should not accumulate as model-visible conversation history. The configured MCP timeout should remain the host-side deadline unless the user cancels or a real failure occurs.

Additional information

  • The official sample config calls code mode "under development and off by default," but the affected thread used it while the local feature state reported it disabled. PR [codex] Add model tool mode selector #25031 documents model-catalog tool-mode selection overriding feature flags, which appears to explain the otherwise unexpected activation.
  • With extension 26.609.30741 / codex-cli 0.140.0-alpha.2, Ask Human was invoked as a direct MCP call. Recorded calls remained pending and then completed successfully after the user responded 65, 88, and 171 minutes later, without an intervening model decision about whether to terminate them.
  • Extension 26.616.30709 also bundles codex-cli 0.142.0-alpha.1, but I did not reproduce the failure on that exact extension build. The affected run was on 26.616.31447.
  • Neither the MCP server nor tool_timeout_sec controls code-mode yield_time_ms. The model authors the optional initial exec pragma and every subsequent wait call; omitted values independently default to 10 seconds.
  • The affected 0.142.0-alpha.1 binary contains the under-development features.code_mode.direct_only_tool_namespaces option, which can keep a configured MCP namespace out of exec. This is a useful per-client mitigation for known special cases, but the MCP server cannot request it, users must identify and configure each namespace manually, and it does not fix the generic long-running MCP execution regression.
  • After adding direct_only_tool_namespaces = ["mcp__ask_human"] and restarting the IDE session, the same tool was invoked directly, remained pending for 298.6 seconds, and completed successfully without a code-mode yield, wait call, or intervening model decision. This confirms that the mitigation works in the affected build while also demonstrating that correct lifetime now depends on per-client knowledge of the generated namespace.
  • MCP progress notifications are not a mitigation: a server can report that work is still pending when the client supplies a progress token, but those notifications do not control code-mode yields, prevent terminate: true, or bind the cell lifetime to tool_timeout_sec.
  • The MCP server's own 86400-second deadline was shown to the user and had not expired.
  • A local workaround is to clarify agent instructions that Script running with cell ID ... is not a tool timeout and must not trigger terminate: true.
  • I also rolled back and pinned extension version 26.609.30741.

A full session ID and raw rollout can be supplied privately

Activity

  1. added
    bugSomething isn't working
    mcpIssues related to the use of model context protocol (MCP) servers
    tool-callsIssues related to tool calling
    model-behaviorIssues related to behaviors exhibited by the model
    configIssues involving config.toml, config keys, config merging, or config updates
    rate-limitsIssues related to rate limits, quotas, and token usage reporting
    on Jun 19, 2026
  2. Necmttn commented on Jun 21, 2026

    @Necmttn

    This crosses two contracts: extension release channel and tool execution mode. Codex could record a per-turn execution contract: extension version, bundled CLI version, CLI prerelease flag, code_mode configured/effective, MCP transport path, and wait token/cost attribution. If the stable extension activates prerelease code-mode routing, the UI should surface that before a tool call starts, especially for long-running MCP calls.


    Generated with ax.

  3. rusproject commented on Jun 22, 2026

    @rusproject

    Any update on this?

  4. cxd40 commented on Jul 14, 2026

    @cxd40

    I can reproduce a closely related failure in the current Windows Codex Desktop app, and it is not limited to the VS Code extension or a long-running MCP server.

    Environment

    • Codex Desktop (Microsoft Store): 26.707.9564.0
    • Bundled Codex CLI actually used by Desktop: 0.144.2
    • Windows: 10.0.26200, x64
    • Model: gpt-5.6-sol, xhigh reasoning for the affected turn
    • Effective local feature state from the active bundled CLI: code_mode=false, code_mode_host=true
    • No explicit code_mode opt-in in config.toml
    • The Desktop app-server process was launched with -c features.code_mode_host=true

    Failure variant

    This involved an ordinary PowerShell verification command invoked through a code-mode exec cell. The command ran eight bounded HTTP checks and legitimately took longer than the initial 10-second yield.

    Sanitized timeline (local time, UTC+8):

    11:18:22  assistant says the upgrade completed and begins final verification
    11:18:51  exec starts the PowerShell verification command
    11:19:01  exec yields: Script running with cell ID 61
    11:19:02  Desktop logs show the next model sampling request begins
    11:19:02–11:53:09  no assistant message, wait call, tool call, completion, WARN, or ERROR
    11:53:09  user sends a status/continue message
    11:53:22  the same active turn immediately resumes and reports that all 8/8 checks succeeded
    

    The turn remained inProgress for roughly 34 minutes. The command itself was healthy and completed successfully. The local app-server and MCP connection-manager continued producing heartbeat/trace activity during the gap, and there were no WARN or ERROR records for the affected thread. Network connectivity was also confirmed after recovery.

    The key difference from the original report is that the model did not issue terminate: true; it simply failed to issue the required wait after receiving Script running with cell ID 61. A user follow-up caused the same turn to resume immediately.

    Why this appears to be the same execution-contract problem

    A yielded cell requires another model inference merely to keep a healthy command alive and collect its result. If that inference stalls or omits wait, Desktop leaves the turn visually active indefinitely even though the underlying command has completed. The UI exposes no bounded timeout, automatic result reconciliation, or recovery action.

    This suggests the runtime should own pending-cell continuation and result collection. At minimum:

    1. A healthy yielded cell should be polled automatically until completion, configured timeout, transport failure, or explicit user cancellation.
    2. A model response should not be required merely to preserve or collect a running tool call.
    3. If the post-yield sampling request produces no action within a bounded interval, Desktop should retry/reconcile the cell and surface a recoverable error instead of leaving the turn in inProgress.
    4. The UI should distinguish model reasoning from waiting on a live cell.

    Local mitigations applied

    • Added a global instruction that Script running with cell ID ... must always be followed by explicit wait calls.
    • Changed Desktop follow-up handling from queue to steer, so a status/continue message can reach an active stuck run without requiring a manual pause.

    These reduce the impact but do not fix the runtime-level failure. I can provide a redacted transcript/log excerpt privately if useful.

  5. shirafukayayoi commented on Jul 23, 2026

    @shirafukayayoi

    Adding a current Codex Desktop / Windows data point where the code-mode lifetime problem compounds with multi_agent_v1 and creates duplicate subagents plus a high-token polling loop.

    Environment

    Codex Desktop package: 26.715.10079.0
    Bundled CLI from session metadata: 0.145.0-alpha.30
    Platform: Windows x64
    Parent model: gpt-5.6-sol, medium reasoning
    Multi-agent mode: v1
    

    This installation uses a custom local Responses base URL. Provider requests completed successfully with unique request/tool-call IDs; the failing exec, wait, and multi_agent_v1 sequence occurred in the local Codex Desktop tool host.

    Observed sequence

    1. The parent spawned three bounded PDF-inspection subagents.

    2. The user asked to replace those workers with a vision-capable model.

    3. close_agent successfully shut down the original three workers.

    4. The first replacement spawn failed schema validation, then a corrected three-agent spawn batch was run inside code-mode exec.

    5. The outer exec yielded after about 11 seconds:

      Script running with cell ID 19
      
    6. Instead of preserving and waiting on cell 19, later model turns started additional spawn attempts for the same three page ranges. This created another worker set and eventually produced:

      collab spawn failed: agent thread limit reached
      
    7. Once workers existed, the parent repeatedly wrapped multi_agent_v1__wait_agent in new exec calls. Each outer call yielded after about 11 seconds and allocated another cell:

      cell IDs 23, 24, 26, 27, 30, 31, 32, 34, 36, 37, 38, 39
      
    8. The parent emitted essentially the same progress message about switching models and waiting for the same page ranges about 30 times in roughly eight minutes.

    9. At least one subagent later delivered a valid completion notification. The parent nevertheless attempted to wait on an earlier completed cell and received:

      exec cell 19 not found
      
    10. By that point the parent session had consumed approximately 3.5 million input tokens, primarily cached context, without reaching the integration/write step.

    The repeated waits were not repeated user requests. The only user follow-up was the one model-switch request.

    Why this appears related

    This is the same architectural failure described here: a healthy long-running nested operation is converted into a model-visible exec cell, every yield forces another inference, and correctness depends on the model preserving exactly one cell/operation identity across turns. With multi_agent_v1, a wrong decision is more damaging than terminating one MCP call: it can duplicate spawn batches, fill the agent pool, create many simultaneous wait cells, and repeatedly burn the full parent context.

    It also overlaps with:

    This variant differs because the parent often starts a fresh nested wait_agent operation and therefore receives a fresh exec cell ID each time, rather than only retrying one stale/noop cell.

    Expected behavior / possible fix shape

    • multi_agent_v1 lifecycle tools, especially spawn_agent, wait_agent, and close_agent, should not require model-driven code-mode cell keepalive loops.
    • A yielded nested operation should retain a runtime-owned identity and reject or coalesce a duplicate invocation for the same agent set/range.
    • Waiting for subagents should not require a new model inference every 10 seconds or append every intermediate wait to conversation history.
    • If code mode remains involved, the app should surface one durable in-flight handle and prevent another spawn/wait for the same operation until that handle completes, fails, or is explicitly cancelled.
    • A completed subagent notification should reconcile outstanding wait cells so later waits do not produce stale-cell recovery loops.

    I can provide a more heavily redacted rollout excerpt if maintainers need the exact event ordering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingconfigIssues involving config.toml, config keys, config merging, or config updatesextensionIssues related to the VS Code extensionmcpIssues related to the use of model context protocol (MCP) serversmodel-behaviorIssues related to behaviors exhibited by the modelrate-limitsIssues related to rate limits, quotas, and token usage reportingtool-callsIssues related to tool calling

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions