Bug: app-server can retain orphaned threads after last-subscriber disconnect

Open 💬 2 comments Opened Mar 9, 2026 by agustif

Summary

I’m opening this issue both to report the bug and to ask what the expected external contributor flow is for getting the patch upstream.

Bug

When the last subscribed client connection disappears unexpectedly, app-server removes the connection bookkeeping but can leave the underlying thread loaded.

That diverges from explicit thread/unsubscribe, which already unloads the thread when the last subscriber leaves.

In practice, this leaves zero-subscriber threads resident longer than necessary and can also leave stale app-server bookkeeping behind in late-shutdown edge cases.

Why this matters

This is a real retention path for subagent-heavy and long-lived desktop sessions:

  • unexpected disconnects can strand loaded threads with no subscribers
  • stale thread/watch state can outlive the core thread
  • the behavior is inconsistent with the existing explicit unsubscribe path

Proposed fix

The patch I prepared is intentionally narrow and stays inside codex-rs/app-server.

Behaviorally it does three things:

  1. ThreadStateManager::remove_connection returns the ThreadIds that became orphaned when the disconnect removed the last subscriber.
  2. CodexMessageProcessor::connection_closed routes those orphaned threads through the same unload path already used by explicit thread/unsubscribe.
  3. Late ShutdownComplete events reconcile stale thread/watch state if the core thread was already removed, while preserving the existing thread/closed semantics for that path.

Scope:

  • no protocol changes
  • no schema changes
  • no docs changes
  • no generated files

Validation

I implemented the patch in my fork and validated it locally with:

  • cargo test -p codex-app-server
  • just fmt
  • just fix -p codex-app-server

Per repo rules, I did not rerun tests after the final just fix.

Branch / commit

Tests included in the patch

  • non-orphan disconnect preserves the listener when another subscriber remains
  • closed connections cannot be auto-subscribed back into a thread
  • last-subscriber disconnect reports the orphaned thread id
  • ShutdownComplete after forced unload clears stale state and emits thread/closed

Contributor-flow question

I attempted to create the upstream PR with GitHub CLI and got:

GraphQL: agustif does not have the correct permissions to execute CreatePullRequest

If external contributions are accepted here, could you clarify the expected flow or the permission/setup needed to submit this patch as a PR?

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗