VS Code: support per-chat Codex app-server lifecycle instead of global window reload

Resolved 💬 2 comments Opened May 15, 2026 by lcv-leo Closed May 15, 2026
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

Summary

Please add a VS Code lifecycle equivalent to Claude Code's extension: each Codex chat/editor panel should own the backend process/session resources it needs, and closing that panel should shut those resources down, including the local codex.exe app-server and its child processes when they are panel-scoped.

Today the OpenAI/Codex VS Code extension appears to start one shared local Codex app-server for the entire extension host. That makes a Codex reload require either a global VS Code window reload or a singleton app-server restart, both of which are operationally worse than Claude Code's per-panel lifecycle.

Why this matters

I often run multiple agents simultaneously in the same VS Code window, for example Claude Code plus ChatGPT/Codex. When only the Codex agent needs a reload, such as after an MCP server/config change, using Developer: Reload Window interrupts every active agent and extension in the window.

Claude Code's VS Code extension behaves better for this workflow: closing the Claude chat tab tears down the resources for that specific chat, including claude.exe, so I can restart only that agent without disrupting the rest of VS Code.

Environment

  • OS: Windows
  • VS Code extension: openai.chatgpt version 26.513.21555 (openai.chatgpt-26.513.21555-win32-x64)
  • Claude comparison extension: anthropic.claude-code version 2.1.142 (anthropic.claude-code-2.1.142-win32-x64)
  • Codex binary path observed locally: ~/.vscode/extensions/openai.chatgpt-26.513.21555-win32-x64/bin/windows-x86_64/codex.exe
  • Claude binary path observed locally: ~/.vscode/extensions/anthropic.claude-code-2.1.142-win32-x64/resources/native-binary/claude.exe

Observed behavior in Claude Code VS Code extension

From the installed Claude extension bundle, the panel path creates a per-panel communication/process object:

  • setupPanel(...) creates a new m3(...) instance for the WebviewPanel.
  • The panel dispose hook calls H.shutdown():
  • z.onDidDispose(() => { H.shutdown(); ... })
  • m3.shutdown() aborts active controllers, closes all channels, drains pending channel ops, and tears down the subprocess path.
  • The lower process transport closes stdin and sends termination (SIGTERM, then SIGKILL after timeout) if the process remains alive.

Operationally, closing a Claude Code chat tab terminates the corresponding claude.exe process tree.

Observed behavior in OpenAI/Codex VS Code extension

From the installed OpenAI/Codex extension bundle:

  • Activation creates a single app-server connection:
  • let d = new I_(t.extensionUri, l);
  • let m = d.startCodexProcess();
  • That singleton d is then passed into shared providers and the main webview provider, including wl, R_, dR, auth/preview/fetch paths, etc.
  • The main provider stores one this.codexMcpConnection and uses it across sidebar and editor panels.
  • registerOnDidDisposeForWebviewPanel(...) clears pending webview requests and removes the panel, but does not shut down a panel-specific app-server because there is no panel-specific app-server.
  • The webview message codex-app-server-restart currently routes to workbench.action.reloadWindow, which reloads the whole VS Code window instead of only Codex.

This means closing a Codex task/editor tab does not terminate codex.exe; the process remains shared by the whole extension. If Codex needs a backend reload, the current UX pushes toward a full window reload, interrupting other agents.

Requested behavior

Preferred:

  • Make each Codex chat/editor panel/session own a CodexMcpConnection / app-server lifecycle, similar to Claude Code's m3 per-panel model.
  • On WebviewPanel.onDidDispose / custom editor dispose, gracefully shut down the backend process/session for that panel.
  • Ensure subprocesses are terminated as part of the panel lifecycle.
  • Reopening the chat should create a fresh app-server/session without requiring workbench.action.reloadWindow.

Acceptable fallback if a fully per-panel process model is not currently viable:

  • Add an official Restart Codex App Server command for the VS Code extension that restarts only the Codex app-server process and preserves the VS Code extension host/window.
  • Replace the current codex-app-server-restart -> workbench.action.reloadWindow behavior with that narrower restart path.
  • Avoid using full window reload for settings/config changes that only require the Codex backend to re-read config, such as WSL/MCP/app-server runtime changes.

Implementation notes from local inspection

The current singleton design makes a trivial local patch unsafe for exact parity. Killing the singleton codex.exe from a single panel's dispose hook would also kill any other Codex panel using the same CodexMcpConnection. Real parity needs the provider to route MCP requests/responses/notifications through a connection associated with the owning webview/panel/session, or needs an explicit supported app-server restart API with clear UX that it is global to Codex but not global to VS Code.

Expected outcome

A user should be able to reload/restart ChatGPT/Codex inside VS Code without reloading the entire VS Code window and without interrupting other active agents such as Claude Code.

View original on GitHub ↗

2 Comments

lcv-leo · 3 months ago

Update: local investigation found a working per-tab lifecycle patch

I continued investigating this locally on Windows with the installed openai.chatgpt VS Code extension bundle (openai.chatgpt-26.513.21555-win32-x64). The original issue description is still accurate, but the root cause and the required shape of the fix are now clearer.

Confirmed target behavior after local patch

After patching the installed extension bundle locally and reloading the VS Code extension host, the behavior now matches what I expected from Claude Code:

  • Opening VS Code no longer starts codex.exe for the Codex extension by itself.
  • Opening a new Codex chat tab starts a dedicated codex.exe app-server for that tab.
  • That tab-scoped app-server loads the MCP child-process tree.
  • Closing that Codex chat tab terminates the corresponding codex.exe and its MCP child processes.
  • Opening multiple Codex tabs creates independent process trees; closing one tab terminates only that tab's process tree.
More precise findings

In the installed/minified bundle, the local fix required all of these pieces together:

  1. Do not start the singleton Codex app-server during extension activation.
  2. Create a Codex app-server / MCP connection per chat webview or custom editor, keyed by the owning webview.
  3. Route webview MCP traffic through the connection associated with that webview rather than always through the singleton connection.
  4. On WebviewPanel.onDidDispose / custom editor dispose, dispose the connection for that webview and call the app-server teardown path.
  5. On Windows, teardown needs to terminate the full child-process tree, not only the root codex.exe, because MCP server children otherwise remain orphaned.

The final bug that kept the local patch from being fully correct was specific to new Codex tabs:

  • New tabs opened through the custom editor route /extension/panel/new enter resolveCustomEditor with conversationId == null.
  • The code path created the per-webview Codex connection for that tab, but the dispose registration was still conditional on conversationId != null.
  • That meant a new tab could start codex.exe and MCP children, but closing the tab did not call the per-webview teardown.

The final local fix was to register the dispose handler unconditionally for the custom editor panel, even when conversationId is null, and to capture the webview before disposal so the same webview key can be used to look up and tear down the per-tab connection.

Conceptually:

// Before: new tabs without a conversation id did not get this dispose handler.
if (conversationId != null) {
  registerOnDidDisposeForWebviewPanel(panel, conversationId);
}

// After: every chat/custom-editor panel gets a dispose handler.
registerOnDidDisposeForWebviewPanel(panel, conversationId);

And inside the dispose registration, the important detail was preserving the webview key before the panel is disposed:

const webview = getWebviewForPanel(panel);
panel.onDidDispose(() => {
  const targetWebview = webview ?? getWebviewForPanel(panel);
  disposeCodexMcpConnectionForWebview(targetWebview);
});

The local Windows teardown hardening used a recursive process-tree cleanup rooted at the app-server PID (Get-CimInstance Win32_Process / descendant traversal, children first, root last), with taskkill /PID <pid> /T /F as a fallback.

Validation performed locally
  • node --check passed on the patched installed extension.js bundle.
  • Static marker checks confirmed the old guarded custom-editor dispose registration was gone and the unguarded registration existed exactly once.
  • Runtime smoke test after reload confirmed the expected behavior: no startup codex.exe, per-tab process/MCP trees on tab open, and full process-tree cleanup on tab close.
Upstream suggestion

For the official extension, I think the durable fix should be a source-level version of this lifecycle ownership model:

  • Avoid activation-time app-server startup unless an actual Codex surface requires it.
  • Treat each chat/editor webview as the owner of its backend app-server/session resources, or at least provide a supported per-Codex-surface restart path that does not reload the whole VS Code window.
  • Always register panel/custom-editor dispose cleanup, including for new tabs that do not yet have a conversation id.
  • Ensure Windows process-tree termination includes MCP server children.

I cannot provide a clean PR because the locally validated target was the installed/minified VSIX bundle, not a reproducible extension source package, but this should narrow down the lifecycle issue substantially.

etraut-openai contributor · 3 months ago

Thanks for the suggestion, but we're unlikely to change this. It would be a big architectural change, and it wouldn't benefit most users.