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
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).
Context
PR #381 keys broker cleanup on the owning session(s):
broker.jsonrecordssessionIds, a reused broker adds the reusing session as a co-owner, andteardownBrokersForSessionshuts 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
SessionEndhook (SIGKILL, OOM, crash, host reboot), itssessionIdstays inbroker.jsonforever. When the other owners later exit gracefully,teardownBrokersForSessionsees 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.ppidcaptured atSessionStart) and pruned owners whose pid was gone. That was reverted because: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)).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.mjsself-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