Skip to content

app-server websocket disconnect leaves orphaned MCP-backed threads loaded #16660

Closed as duplicate

Description

@kmatsunami

Summary

When the last app-server websocket connection drops without sending thread/unsubscribe, the loaded thread remains in memory and its MCP child process stays alive.

The root cause appears to be that connection_closed removes connection bookkeeping but does not unload threads that now have zero subscribers. thread/unsubscribe already has the correct unload path, but disconnects do not go through it.

Impact

In the desktop app this lets MCP-backed threads accumulate under the long-lived app-server process. Over time that leaves large numbers of retained biz-cosense-mcp-server, playwright-mcp, and wrapper processes alive after their owning connections are gone.

Reproduction

A deterministic regression test can reproduce this with a real stdio MCP child process:

  1. Start app-server in websocket mode with a stdio MCP server configured.
  2. Connect websocket client A and initialize it.
  3. Start a thread so the MCP stdio child process is launched.
  4. Verify the thread appears in thread/loaded/list and the MCP child process is alive.
  5. Close websocket client A without sending thread/unsubscribe.
  6. Reconnect as websocket client B and inspect thread/loaded/list.

Expected

  • The last disconnect should unload the orphaned thread.
  • The MCP stdio child process should exit.
  • A subsequent thread/loaded/list should be empty.

Actual

  • The thread remains loaded after the last websocket disconnect.
  • The MCP stdio child process stays alive.
  • The new regression test times out waiting for the child process to exit.

Evidence

Before the fix, this targeted test fails:

mise exec rust@1.93.0 -- cargo test -p codex-app-server websocket_disconnect_unloads_thread_and_its_mcp_process -- --nocapture

Failure mode:

  • wait_for_process_exit(&pid) times out after websocket close.
  • Reconnecting still shows the thread in thread/loaded/list.

Acceptance Criteria

  • connection_closed unloads threads whose last subscriber disappeared.
  • The disconnect path reuses the same thread teardown logic already used by thread/unsubscribe.
  • A regression test covers websocket disconnect cleanup with a real stdio MCP child process and passes.
  • Existing thread_unsubscribe tests continue to pass.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    mcpIssues related to the use of model context protocol (MCP) servers
    on Apr 3, 2026
  2. kmatsunami commented on Apr 3, 2026

    @kmatsunami
    Author

    Tested fix is available here:

    I could not open a PR because openai/codex is configured with pull_request_creation_policy: collaborators_only.

    Verified locally:

    • mise exec rust@1.93.0 -- cargo test -p codex-app-server websocket_disconnect_unloads_thread_and_its_mcp_process -- --nocapture
    • mise exec rust@1.93.0 -- cargo test -p codex-app-server removing_last_connection_returns_orphaned_thread -- --nocapture
    • mise exec rust@1.93.0 -- cargo test -p codex-app-server thread_unsubscribe -- --nocapture
  3. removed
    appIssues related to the Codex desktop app
    on Apr 3, 2026
  4. kmatsunami commented on Apr 3, 2026

    @kmatsunami
    Author

    I checked the duplicate suggestions.

    Closest related issues:

    I think this issue is still worth keeping open because it narrows one concrete app-server cause with a deterministic repro and tested fix:

    • when the last websocket client disconnects without thread/unsubscribe, connection_closed drops connection bookkeeping but does not unload the thread that now has zero subscribers
    • that leaves the thread loaded and keeps its MCP stdio child process alive
    • the fix branch here adds a regression test for that exact websocket-disconnect path and routes it through the existing thread teardown logic

    So my read is:

    • #14950 is likely the broader user-facing symptom tracker for the desktop app
    • this issue documents one specific root cause / fixable path under that umbrella

    Fix branch and compare are here if useful for triage:

  5. etraut-openai commented on Apr 3, 2026

    @etraut-openai
    Contributor

    Closing in favor of #14950.

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 workingmcpIssues related to the use of model context protocol (MCP) servers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions