Repository navigation
Description
Context
PR #381 keys broker cleanup on the owning session(s): broker.json records sessionIds, a reused broker adds the reusing session as a co-owner, and teardownBrokersForSession shuts a broker down only once no owner remains. This correctly stops a single session from tearing down a broker its siblings are still using.
Gap
If a co-owning session disappears without running its SessionEnd hook (SIGKILL, OOM, crash, host reboot), its sessionId stays in broker.json forever. When the other owners later exit gracefully, teardownBrokersForSession sees a remaining owner and preserves the broker — but no future hook will ever fire for the dead owner. The shared/worktree broker is orphaned indefinitely.
This is the same class as the original single-owner leak in #108, but surfaced by the new co-ownership model. Codex flagged it on the PR: #381 (comment)
Why not fix it in the SessionEnd hook
PR #381 briefly recorded a per-owner session pid (process.ppid captured at SessionStart) and pruned owners whose pid was gone. That was reverted because:
- The packaged
hooks.jsonrunsSessionStartin shell form (noargs), sonode'sprocess.ppidis the ephemeralshwrapper, not the Claude session. The recorded pid is dead almost immediately, which made every shared-broker co-owner look dead and tore down brokers still in use (regression flagged in fix: tear down brokers by sessionId on SessionEnd to avoid orphaned worktree brokers (#380) #381 (comment)). - Even with a correct anchor,
process.kill(pid, 0)is unsound across PID reuse, and there is no cross-platform way to map asessionIdto a live process (macOS has no/proc; exec-form hooks aren't available in the hook schema).
A hook-side liveness signal is therefore not a reliable fix.
Proposed resolution
Solve it broker-side with the idle timeout already proposed in #108: app-server-broker.mjs self-terminates after N minutes with no active client connection. This is platform-independent, needs no PID/liveness signal, and covers both the abnormal-exit orphan and the dead-co-owner orphan in one mechanism.
This issue tracks the co-owner facet specifically; it can be closed as covered once #108's idle timeout lands (they likely share one implementation).
Related
- Broker process not cleaned up on session exit — no idle timeout #108 — umbrella idle-timeout issue
- SessionEnd cleanup fails when review cwd ≠ session cwd — broker.json looked up by cwd-hash, leaving orphan brokers even on graceful /quit #380 / PR fix: tear down brokers by sessionId on SessionEnd to avoid orphaned worktree brokers (#380) #381 — graceful cwd-mismatch teardown (the in-scope part, fixed)
Activity
hongsu commentedon Jul 8, 2026
Additional trigger for the same dead-owner orphan, found in self-review of PR #381: lock contention, not only abnormal exit.
teardownBrokersForSessionacquires a per-broker.jsonlock with a short timeout (kept under the 5s SessionEnd hook budget). If the ending session A cannot acquire the lock in time (another hook is mid-critical-section), it skips that entry, leavingAinsessionIds.Ahas already run itsSessionEnd, so it never retries. Later, session B ends, removes onlyB, still seesAas a remaining owner, and preserves the broker withoutbroker/shutdown— orphaned indefinitely.This is the same unresolvable core as the crash case: a departed session's
sessionIdlingers inbroker.jsonand later teardown cannot tell it is dead (no reliable cross-platformsessionId→liveness signal). The broker-side idle timeout resolves this variant too, since it does not depend on the ownership records being accurate.So the idle timeout should be treated as the correctness backstop for all stale-owner cases: abnormal exit, dead co-owner, and lock-contention skips. The session-side teardown in PR #381 is a best-effort eager optimization only.
fix(broker): bound SessionEnd cleanup and don't strand the ending owner
fix(broker): best-effort shutdown on bad endpoints; atomic state writes
axisrow commentedon Jul 16, 2026
This is scenario 1 in the orphan-broker map I posted in #163 — the "detached broker reparents to init when the owning session exits without SessionEnd" class. PR #490 (15-min idle-timeout with no clients) covers exactly this: the connection drops → the broker self-exits. I confirmed this class empirically (338 orphaned brokers on macOS, ~2.6 GB freed on cleanup). See #163 for the full three-scenario map and what #490 does not cover.