fix(tui-gateway): interrupt turns after websocket disconnect - #90373
fix(tui-gateway): interrupt turns after websocket disconnect#90373Kyzcreig wants to merge 2 commits into
Conversation
After the existing reconnect grace, route a still-detached running session through the same interrupt mechanism as session.interrupt. Preserve delegation deferral, sidecar teardown, partial history, and single-owner reap semantics. Verified on upstream main: RED 4 failed/2 passed without production changes; GREEN 603 related gateway/compute-host tests. Ruff and py_compile passed. Momus pass 2: APPROVE.
f83b078 to
41c902a
Compare
Reviewed by reviewer-e (AI automated review). The right semantics for a real UX bug: a WS drop no longer orphans a mid-flight turn — the orphan reaper interrupts running turns (compute-host supervisor or in-process agent, clearing queued prompts under the history lock), then re-polls at 1s and only reaps once normal turn finalization settles; active delegations defer the reap; reattachment inside the grace window spares everything; and
|
…nding) If an interrupted turn never settles (agent thread hung in a syscall, supervisor lost), the 1s poll chain rescheduled forever — trading the old leak-one-worker bug for leak-one-session-plus-timer-chain. After _WS_ORPHAN_INTERRUPT_REAP_MAX_POLLS (60 = ~60s, 3x the default grace) the reaper logs loudly and force-reaps, mirroring the pre-existing stuck-running safety net's deadlock-breaking role. Focused suite 4 passed, 1 skipped.
|
Good catch — the unbounded chain was real (each fire unconditionally rescheduled at 1s while |
Summary
session.interruptsession.resumecannot reattach after the grace-expiredclient_goneinterruption claimWhy
On 2026-07-05, a certification client was SIGKILLed while approximately 20 turns were in flight. The WebSocket sessions detached, but
_ws_session_is_orphaned()intentionally returned false whilerunningremained true. Nothing then requested interruption, so the orphaned turns continued consuming compute and wedged the dashboard.This change extends the existing
_close_sessions_for_transport()→_schedule_ws_orphan_reap()teardown path. It does not add a parallel disconnect path or alterclose_on_disconnectsidecar handling.HERMES_TUI_WS_ORPHAN_REAP_GRACE_Sremains the configuration seam; zero still disables the reaper.Race handling
Reconnect or
session.resumeduring the grace still cancels interruption. Once the grace callback atomically claims a detached session asclient_gone, live-session rebinds revalidate session identity under the shared resume lock and reject late attachment instead of resurrecting the claimed session.Verification
On current upstream
mainafter rebasing:py_compile: passed on all changed Python filesgit diff --check: passedAPPROVEThe focused coverage proves isolated and in-process interruption, reconnect cancellation, active-delegation deferral, unchanged sidecar close behavior, and rejection of late resume after the
client_goneclaim.Live AC-13 evidence
A real dashboard WebSocket client created an isolated compute-host turn executing
sleep 120; the parent then hard-killed only the client process on the final integration tree carrying these production changes.-9;kill_confirmed=true16:59:56.012, code 1006,detached_sessions=1client_goneinterrupt:17:00:16.021— 20.009 seconds after disconnect[Command interrupted], exit code 130reason=interrupted_by_user17:00:18.251PASS