Skip to content

fix(acp): restore output_rx on every do_prompt exit path - #6665

Merged
bug-ops merged 1 commit into
mainfrom
fix/6661-acp-output-rx-leak
Jul 27, 2026
Merged

fix(acp): restore output_rx on every do_prompt exit path#6665
bug-ops merged 1 commit into
mainfrom
fix/6661-acp-output-rx-leak

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • do_prompt (crates/zeph-acp/src/agent/turn.rs) only restored entry.output_rx on the success path after drain_agent_events completed. An input_tx.send failure returned early without restoring it, and the enclosing task being aborted while suspended inside drain_agent_events (reachable since ACP permission gate deadlocks on every permission-gated tool call #6656 spawns the turn instead of awaiting it inline) dropped the receiver entirely — both permanently wedged the session with "prompt already in progress" on every subsequent session/prompt call.
  • Introduced PromptChannelGuard, an RAII guard that owns the session's output_rx receiver for the entire do_prompt call and restores it into the session on Drop, covering every exit path (normal return, early return, task abort/drop).
  • Changed drain_agent_events to borrow &mut mpsc::Receiver<LoopbackEvent> instead of consuming it by value, so the guard never loses ownership across the drain loop's await points.

Closes #6661

Test plan

  • New regression test input_tx_send_failure_does_not_wedge_session — reproduces the issue's literal repro (dropped agent-loop channel causes input_tx.send failure), asserts the specific "agent channel closed" error, and confirms the session recovers (acquire_prompt_channels succeeds afterward).
  • New regression test abort_mid_drain_does_not_wedge_session — spawns do_prompt, deterministically parks it inside drain_agent_events's rx.recv(), aborts the task, and confirms the session recovers.
  • New regression test two_consecutive_prompts_succeed_without_wedging_session — happy-path non-regression check.
  • cargo +nightly fmt --check clean
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings clean
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — full suite green
  • Rustdoc gate (RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler") clean
  • Adversarial critique (RAII guard soundness, lock ordering, panic-in-drop, drain-borrow correctness) — verdict significant initially (2 test gaps), both resolved and re-verified
  • Code review — approved, full CI-matching suite independently re-run

do_prompt only restored entry.output_rx on the success path after
drain_agent_events completed. An input_tx.send failure, or the turn's
task being aborted while suspended inside drain_agent_events, left the
receiver permanently unrestored, wedging the session with "prompt
already in progress" on every subsequent request. A PromptChannelGuard
now owns the receiver for the whole do_prompt call and restores it on
Drop, covering every exit path; drain_agent_events borrows the receiver
instead of consuming it so the guard never loses ownership across the
drain loop's await points.
@bug-ops
bug-ops force-pushed the fix/6661-acp-output-rx-leak branch from 7d514a6 to 4d7a5f1 Compare July 27, 2026 22:36
@bug-ops
bug-ops enabled auto-merge (squash) July 27, 2026 22:36
@bug-ops
bug-ops merged commit b8117a2 into main Jul 27, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/6661-acp-output-rx-leak branch July 27, 2026 22:45
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation rust Rust code changes size/L Large PR (201-500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zeph-acp: do_prompt leaks output_rx on input_tx.send failure, wedges session with 'prompt already in progress'

1 participant