Skip to content

fix(acp): close PromptChannelGuard reload race and stale-event leak - #6672

Merged
bug-ops merged 1 commit into
mainfrom
fix/6666-acp-channel-guard-restore
Jul 28, 2026
Merged

fix(acp): close PromptChannelGuard reload race and stale-event leak#6672
bug-ops merged 1 commit into
mainfrom
fix/6666-acp-channel-guard-restore

Conversation

@bug-ops

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

Copy link
Copy Markdown
Owner

Summary

  • SessionEntry now carries a generation stamp (assigned in make_session_entry, the sole construction site for new/load/fork/resume). PromptChannelGuard captures it at acquisition and skips restoring output_rx in Drop if the entry's current generation no longer matches — closes the race where a session reloaded/resumed mid-turn (after a prior close/delete) had its fresh, live output_rx clobbered by a stale receiver from the superseded turn.
  • acquire_prompt_channels now drains any events already queued on the receiver (under the sessions lock, right after output_rx.take() succeeds), in addition to the existing Drop-time drain. A single point-in-time drain in Drop only catches events queued at that instant; the agent loop can keep emitting after Drop returns (e.g. a second Flush), so draining at acquire time closes the whole inter-turn window instead of one snapshot — preventing stale LoopbackEvents from leaking into the next prompt's drain_agent_events and causing a spurious immediate EndTurn.

Both bugs were found during adversarial review of PR #6665 (fix for #6661) and build on top of the PromptChannelGuard it introduced.

Closes #6666
Closes #6667

Test plan

  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 15116 passed, 0 failed
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins -E 'package(zeph-acp)' — 197/197 passed, including 2 new regression tests for zeph-acp: PromptChannelGuard restore can clobber a reloaded session's live output_rx #6666/zeph-acp: restored output_rx after cancel/abort may carry stale queued events into the next prompt #6667 plus a double-Flush regression test for the acquire-time drain
  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • gitleaks protect --staged --no-banner --redact

PromptChannelGuard::drop restored output_rx unconditionally on every
exit path, keyed only by session_id looked up fresh at drop time. If
a session was reloaded/resumed mid-turn (do_load_session/do_resume_session
insert a fresh SessionEntry over the same id after a prior close/delete),
the guard's drop could clobber the new entry's live output_rx with a
dead receiver from the superseded turn.

Stamp SessionEntry with a monotonically increasing generation from
make_session_entry, the sole construction site for all session-creation
paths. PromptChannelGuard captures the generation at acquisition and
skips the restore in Drop if the entry's current generation no longer
matches.

Separately, a receiver restored after task abort/cancel could carry
LoopbackEvents the still-alive agent loop had already queued (or would
still queue after Drop returns, e.g. a second Flush after an await
point), leaking into the next prompt's drain_agent_events and causing
a spurious immediate EndTurn. Drain the receiver in both Drop (cheap
early filter) and acquire_prompt_channels (under the sessions lock,
right after output_rx.take() succeeds, closing the full inter-turn
window rather than one instant).

Closes #6666
Closes #6667
@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Rust code changes bug Something isn't working size/L Large PR (201-500 lines) labels Jul 28, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 28, 2026 00:41
@bug-ops
bug-ops merged commit dbc82b3 into main Jul 28, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/6666-acp-channel-guard-restore branch July 28, 2026 00:49
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

1 participant