fix(acp): abort orphaned agent loops and deliver /review output - #6684
Merged
Conversation
bug-ops
enabled auto-merge (squash)
July 28, 2026 11:33
bug-ops
force-pushed
the
fix/6674-acp-session-turn-race
branch
from
July 28, 2026 11:33
01133e7 to
1dd115c
Compare
do_close_session/do_delete_session previously only signaled cancel_signal.notify_one() without joining or aborting the session's agent-loop task, so a reload/resume of the same SessionId could race against a still-running old loop and corrupt the new turn's event stream. SessionEntry now tracks the loop's JoinHandle; close/delete abort and await it (5s bounded timeout) before the entry is dropped, and Drop for SessionEntry aborts it unconditionally as a safety net for the LRU-eviction/reaper removal paths. /review previously dispatched fire-and-forget via input_tx.try_send and returned EndTurn immediately, bypassing acquire_prompt_channels. After the prior turn-race fix started draining queued events at acquire time, /review's output was silently discarded instead of leaking into the next turn. /review is now intercepted in do_prompt and routed through the normal acquire_prompt_channels/drain turn path like any other prompt, so its output reaches the client. Closes #6674 Closes #6673
bug-ops
force-pushed
the
fix/6674-acp-session-turn-race
branch
from
July 28, 2026 11:45
1dd115c to
6870cbf
Compare
bug-ops
added a commit
that referenced
this pull request
Aug 16, 2026
* docs(readme): sync crate READMEs with commits since v0.22.3 Reconciles all 24 changed crate READMEs against the actual shipped implementation for the v0.22.3..HEAD range: new subsystems (risk-chain detection, capability scoping, plugin dependency graph, session spawn cap), several pre-existing factual errors unrelated to this release (inverted file-sandbox precedence, fabricated MCP config keys, wrong anomaly-detector defaults, stale trust-level names), and terminology/ API renames that had drifted out of sync with the code. * docs(specs): reconcile spec drift for commits since v0.22.3 Closes drift left after the skill-quarantine trust fixes (#6701, #6702, #6706, #6707, #6713), the subagent session-wide spawn cap (#6545), four post-ACP-2.0.0-migration bugfixes (#6660, #6665, #6672, #6684), the mention-picker and TUI interrupt-hint updates, the MAX_RETRY_SECS compile-time bound, the sanitizer secret-shape masking extension, the tracing-guard-flush invariants, and the VigilGate per-process pattern-compile fix. Updates specs/README.md's index to match. * docs(book): sync user docs with commits since v0.22.3 Updates the TUI keybindings and mention-picker pages for the new Ctrl+C semantics, the inline @ mention picker, and the input separator's busy indicator; documents the new [tools.shell] risk_chain_window_turns config key; corrects the ACP protocol version reference (was stale at 0.11.1); bumps the sub-agent frontmatter breaking-change note to v0.22.4. * fix(serve): give build_combined_deps_wires_policy_gate test a dedicated stack cargo nextest run --features full could crash with a stack overflow (SIGABRT) on serve::agent_factory::tests::build_combined_deps_wires_policy_gate_through_to_session_agent. Same defect class already fixed once in this file for issue #6699: building a full Agent under --features full's unboxed AnyProvider variants (Candle/Gonka/Cocoon) reaches the same VigilGate::try_new stack depth that overflows the default 2 MiB test-thread stack in an unoptimized build. The #6699 fix only wrapped the one test it was filed against, leaving this one - added in PR #6007, unrelated to any change in this release - unprotected. CI's test job never caught it because it runs the curated feature set, not full, so the deeper AnyProvider frames never materialize there. Runs the test body on a dedicated 32 MiB-stack thread instead of directly under #[tokio::test], reusing the existing TEST_THREAD_STACK_SIZE constant. * release: prepare v0.22.4 Bump version across the workspace, finalize the CHANGELOG.md [0.22.4] section, refresh the README tests badge, and re-accept the splash-screen snapshots (embed the version string).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
do_close_session/do_delete_sessionpreviously only firedcancel_signal.notify_one()without joining or aborting the session's agent-loop task, so asession/load/session/resumereusing the sameSessionIdcould race a still-running old loop and corrupt the new turn's event stream.SessionEntrynow tracks the loop'sJoinHandle; close/delete abort and await it (5s bounded timeout) before the entry is dropped, andDrop for SessionEntryalso aborts it unconditionally as a safety net covering the LRU-eviction/reaper removal paths./reviewpreviously dispatched fire-and-forget viainput_tx.try_sendand returnedEndTurnimmediately, bypassingacquire_prompt_channels. After PR fix(acp): close PromptChannelGuard reload race and stale-event leak #6672's inter-turn drain fix,/review's output was silently discarded instead of leaking into the next turn./reviewis now intercepted indo_promptand routed through the normalacquire_prompt_channels/drain turn path like any other prompt, so its output actually reaches the client. This is a client-visible behavior change:/reviewnow participates in the same turn-contention check as any other prompt (can be rejected with "prompt already in progress") and its expanded prompt is persisted/replayed as part of session history.Both issues were follow-up findings from PR #6672's review. Went through two rounds of adversarial critique: the first found a real gap where
set_agent_loop_handlecould silently drop aJoinHandleif the session entry was removed before the loop was fully wired up (fixed: aborts immediately instead), and a/reviewtrailing-whitespace parsing regression (fixed: trim both ends to matchhandle_slash_command's existing parity). The second round re-verified both fixes and found no new gaps.Closes #6674
Closes #6673
Test plan
cargo +nightly fmt --checkcargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warningscargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler"(zeph-acp, zeph-common, zeph-core scopes) — 2736 passedcargo nextest runfullzeph-acppackage scope (includingtests/integration.rs, not just--lib --bins) — 235/235 passed, including all 5 new/changed regression tests:close_session_aborts_agent_loop_task,delete_session_aborts_agent_loop_task(prove the old loop is actually aborted+joined, not just signaled)review_command_output_is_delivered_to_client(positive round-trip proving/review's output reaches the client)review_command_rejects_when_a_turn_is_already_in_progress(loops over["/review", "/review\n", "/review\t"]to lock in the trim fix)review_command_threads_connection_owner_key_into_channel_message(now also asserts the forwarded text matches the expanded review prompt)RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"gitleaks protect --stagedCHANGELOG.mdupdated under[Unreleased]/Fixed