Skip to content

zeph-acp: PromptChannelGuard restore can clobber a reloaded session's live output_rx #6666

Description

@bug-ops

Description

Follow-up from #6661 (fixed in PR #6665). PromptChannelGuard::drop (crates/zeph-acp/src/agent/turn.rs) writes the receiver it holds back into entry.output_rx unconditionally, keyed only by session_id, looked up fresh via self.state.sessions.lock().get(&self.session_id) at drop time.

If a session is reloaded/resumed mid-turn — do_load_session (session.rs:440) or do_resume_session (session.rs:828) both insert() a fresh SessionEntry over the same SessionId — the guard's Drop will still find an entry under that id and overwrite its (new, live) output_rx with the old, now-dead receiver from the turn that was in flight before the reload. The reloaded session then ends up draining a receiver whose sender belongs to an agent loop that no longer exists.

This is a widening of a pre-existing behavior (the original success-path restore had the same unconditional write), not a new bug introduced by #6661's fix — but #6661 widens the exposure window because the abort/cancel path now also restores, and that window is asynchronous with respect to when .abort() is called, giving more opportunity for a do_load_session/do_resume_session race to land inside it.

Expected Behavior

PromptChannelGuard::drop should only restore into entry.output_rx if the entry it finds is still the same session generation/instance the guard was originally constructed against — not any entry that merely shares the same SessionId string.

Suggested Fix Direction

Add a generation counter (or an Arc/pointer identity check) to SessionEntry, captured by the guard at construction time, and compared before writing back in Drop. If the generation doesn't match, skip the restore (the reloaded session already owns a fresh, valid output_rx).

Environment

Logs / Evidence

Found during independent adversarial critique of PR #6665 (finding M2). See PR #6665 review discussion for the full analysis.

Metadata

Metadata

Assignees

Labels

P3Research — medium-high complexitybugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions