You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Shared/co-owned broker orphaned when an owning session exits without SessionEnd #450
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:
Even with a correct anchor, process.kill(pid, 0) is unsound across PID reuse, and there is no cross-platform way to map a sessionId to 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).
Additional trigger for the same dead-owner orphan, found in self-review of PR #381: lock contention, not only abnormal exit.
teardownBrokersForSession acquires a per-broker.json lock 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, leaving A in sessionIds. A has already run its SessionEnd, so it never retries. Later, session B ends, removes only B, still sees A as a remaining owner, and preserves the broker without broker/shutdown — orphaned indefinitely.
This is the same unresolvable core as the crash case: a departed session's sessionId lingers in broker.json and later teardown cannot tell it is dead (no reliable cross-platform sessionId→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.
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.
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.
fix(broker): self-terminate on idle to reap orphaned shared brokers (o…