Description
Follow-up from PR #6672 (fix for #6666/#6667), noted by impl-critic during review (M3, out of scope for that PR).
do_close_session/do_delete_session (crates/zeph-acp/src/agent/session.rs) remove a session's map entry without waiting for or aborting any turn still in flight on it — they only call notify_one() (session.rs:303, :343). If the session is then reloaded/resumed (do_load_session/do_resume_session) under the same SessionId, the old agent loop task is never joined or aborted; it can keep running and emit LoopbackEvents during the new session generation's turn.
PR #6672 already closes the narrower race where the old turn's guard would clobber the new entry's output_rx on Drop (via the generation stamp), and closes the inter-turn stale-event leak via draining in acquire_prompt_channels. But if the old agent loop is still actively producing events while a new turn on the reloaded session is in progress (not just in the gap between turns), those events can still interleave with and corrupt the new turn's drain_agent_events — the generation check and the drains only guard the channel handoff, not the fact that two agent loops can be alive for one SessionId at once.
Expected Behavior
do_close_session/do_delete_session should join or abort any turn in flight on the session before (or as part of) removing its entry, so a reload/resume can never have a stale agent loop still producing events for a SessionId it no longer owns.
Suggested Fix Direction
Track the in-flight turn's JoinHandle/abort mechanism on SessionEntry (or via the existing cancel-signal path used by do_cancel) and have close/delete abort it and await completion (with a bounded timeout) before the entry is removed, rather than firing a bare notify_one().
Environment
Logs / Evidence
Found during independent adversarial critique of PR #6672. See PR #6672 review discussion for the full analysis (finding M3).
Description
Follow-up from PR #6672 (fix for #6666/#6667), noted by impl-critic during review (M3, out of scope for that PR).
do_close_session/do_delete_session(crates/zeph-acp/src/agent/session.rs) remove a session's map entry without waiting for or aborting any turn still in flight on it — they only callnotify_one()(session.rs:303, :343). If the session is then reloaded/resumed (do_load_session/do_resume_session) under the sameSessionId, the old agent loop task is never joined or aborted; it can keep running and emitLoopbackEvents during the new session generation's turn.PR #6672 already closes the narrower race where the old turn's guard would clobber the new entry's
output_rxonDrop(via thegenerationstamp), and closes the inter-turn stale-event leak via draining inacquire_prompt_channels. But if the old agent loop is still actively producing events while a new turn on the reloaded session is in progress (not just in the gap between turns), those events can still interleave with and corrupt the new turn'sdrain_agent_events— the generation check and the drains only guard the channel handoff, not the fact that two agent loops can be alive for oneSessionIdat once.Expected Behavior
do_close_session/do_delete_sessionshould join or abort any turn in flight on the session before (or as part of) removing its entry, so a reload/resume can never have a stale agent loop still producing events for aSessionIdit no longer owns.Suggested Fix Direction
Track the in-flight turn's
JoinHandle/abort mechanism onSessionEntry(or via the existing cancel-signal path used bydo_cancel) and have close/delete abort it and await completion (with a bounded timeout) before the entry is removed, rather than firing a barenotify_one().Environment
crates/zeph-acp/src/agent/session.rs(do_close_session,do_delete_session),crates/zeph-acp/src/agent/turn.rs(PromptChannelGuard, generation check added in PR fix(acp): close PromptChannelGuard reload race and stale-event leak #6672),crates/zeph-acp/src/agent/mod.rs(do_cancel, fire-and-forgetnotify_one())Logs / Evidence
Found during independent adversarial critique of PR #6672. See PR #6672 review discussion for the full analysis (finding M3).