src/arcp/_runtime/_accept.py:23 says the accept loop drives the read pump and heartbeat under a TaskGroup, but run_session calls _maybe_start_heartbeat(ctx) at src/arcp/_runtime/_accept.py:31 and ignores the returned task. _maybe_start_heartbeat creates the heartbeat task with plain asyncio.create_task at src/arcp/_runtime/_accept.py:61, outside the TaskGroup and without any later cancellation or awaiting. When heartbeat loss is detected, heartbeat_loop sets HeartbeatLostError on ctx.heartbeat_outcome at src/arcp/_runtime/session.py:218 and returns, but nothing awaits that future and the blocked read pump remains alive on the transport. This means heartbeat loss can log a warning without actually tearing down the dead session.
Fix prompt: Make heartbeat supervision part of the session task lifecycle. Start the heartbeat inside the TaskGroup or track the task and cancel it in finally, and have heartbeat loss propagate by raising an exception or closing the transport so the read and write pumps exit. Add an integration test that negotiates heartbeat, uses a silent peer that stops responding, and asserts that runtime.accept returns, the transport is closed, and the session is removed from runtime._sessions.
src/arcp/_runtime/_accept.py:23says the accept loop drives the read pump and heartbeat under aTaskGroup, butrun_sessioncalls_maybe_start_heartbeat(ctx)atsrc/arcp/_runtime/_accept.py:31and ignores the returned task._maybe_start_heartbeatcreates the heartbeat task with plainasyncio.create_taskatsrc/arcp/_runtime/_accept.py:61, outside theTaskGroupand without any later cancellation or awaiting. When heartbeat loss is detected,heartbeat_loopsetsHeartbeatLostErroronctx.heartbeat_outcomeatsrc/arcp/_runtime/session.py:218and returns, but nothing awaits that future and the blocked read pump remains alive on the transport. This means heartbeat loss can log a warning without actually tearing down the dead session.Fix prompt: Make heartbeat supervision part of the session task lifecycle. Start the heartbeat inside the
TaskGroupor track the task and cancel it infinally, and have heartbeat loss propagate by raising an exception or closing the transport so the read and write pumps exit. Add an integration test that negotiates heartbeat, uses a silent peer that stops responding, and asserts thatruntime.acceptreturns, the transport is closed, and the session is removed fromruntime._sessions.