Skip to content

App-server broker retains thread subscriptions after task clients disconnect #706

Description

@thossullivan

Summary

The app-server broker shares one upstream connection across plugin tasks.

Each task starts or resumes a thread, which subscribes that upstream connection.

The downstream task socket closes after completion.

The broker clears routing state but never sends thread/unsubscribe.

Completed threads and their MCP runtimes remain loaded until unsubscribe or app-server shutdown.

Observed impact

  • Fresh tasks accumulate MCP runtimes inside one broker session.
  • One app-server retained 11 Basic Memory runtimes.
  • Other app-servers retained seven, five, and five runtimes.
  • Basic Memory only made the generic broker defect visible.
  • The proposed fix contains no Basic Memory dependency.

Reproduction

  1. Configure any stdio MCP server in Codex.
  2. Start Claude Code with the Codex plugin.
  3. Run several fresh Codex tasks in one workspace.
  4. Let every task finish normally.
  5. Inspect the app-server process tree.

Observed result

  • Each fresh task creates another thread and MCP runtime.
  • The downstream task client disconnects.
  • The upstream thread subscription remains active.
  • Completed tasks therefore accumulate MCP processes.

Expected behavior

The broker should track thread ownership for each downstream socket.

It should send thread/unsubscribe after the final owner disconnects.

Cause

thread/start subscribes the shared upstream connection to thread events.

The broker does not associate that subscription with its downstream owner.

Socket closure therefore removes routing state without removing the subscription.

The final unsubscribe starts Codex's documented 30-minute inactivity period.

Codex unloads the thread and MCP runtime after that period.

App-server documentation

Proposed fix

  • Track subscribed threads for each downstream socket.
  • Preserve subscriptions while another downstream owner remains.
  • Cover started, resumed, forked, review, and automatically created child threads.
  • Unsubscribe after normal or abrupt final-owner disconnection.
  • Log upstream unsubscribe failures.
  • Add broker ownership regression tests.

Real-process verification

Tested with Codex CLI 0.150.1 and Basic Memory.

Case Probe after client closure Meaning MCP process result
Control broker unsubscribed The broker retained the subscription Exited approximately 30 minutes after the probe unsubscribed
Patched broker notSubscribed The broker had already unsubscribed Exited approximately 30 minutes after client closure

Both cases started a real Basic Memory wrapper and Python server.

Every isolated test descendant exited during cleanup.

Related work

Those changes bound session lifetime.

This issue addresses per-task thread retention inside a running broker.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions