Claude ACP session stuck after cancelling a turn
- Ngôn ngữ chính
- Rust
- Star
- 54.2k
- Fork
- 6.2k
- Merge trung bình
- 3 ngày 2 giờ
- Pull request đã merge (30 ngày)
- 262
Mô tả
**Describe the bug**
A Berd user interrupts a session midturn to send another message, and the session silently stalls for as long
as the abandoned turn happens to run — 10 minutes in the reported case.
The affected internal user volunteered **the full chat session export**. Reach out to me for more info as I don't want to post that publicly.
What the user experienced:
- Agent was mid-turn, waiting on approval for a shell command.
- User typed a replacement message ("Insert real links. Just rewrite the entire doc.").
- Session went completely silent — no output, no tool calls, no error, no notification.
- Typed "What are you doing?" 3 minutes later — no reply either.
- ~10 minutes after the original message, output resumed and the task completed
normally, with no indication anything had gone wrong.
- Their summary: *"I didn't see any errors or notifications, you just sat there."*
- Only working remedy was restarting Berd — which kills the goosed sidecar and
the agent child, discarding in-flight work.
What the agent was doing during those 10 minutes (invisible to the user):
- Still executing the abandoned turn.
- Every approval-requiring tool failed instantly with
`Tool permission request failed: Error: Internal error`.
- Looped sleep-and-retry trying to make progress — which is what stretched the
stall to 10 minutes.
Cause: goose tears down its side of a cancelled/replaced turn but never sends
`session/cancel` to the ACP agent child (`crates/goose/src/acp/provider.rs`
contains no `CancelNotification` anywhere). The zombie turn keeps running, and:
- child `session/request_permission` → bare JSON-RPC **-32603 Internal error**
(`provider.rs:1389–1408`), unlogged. Claude Code renders it to the model as
the `Tool permission request failed` above.
- child `session/update` → silently dropped (`let _ = tx.try_send(...)`);
nothing persisted, nothing forwarded to the frontend.
- user's replacement prompt → queued invisibly; `handle_requests` is strictly
serial and stays parked awaiting the child's `session/prompt`
(`provider.rs:1697–1725`).
- no timeout or safeguard on this path — recovery is just the zombie turn
ending on its own, after which the queued prompt dispatches on a fresh channel.
Scope: provider-layer, so expected to affect every ACP-backed provider
(claude-acp, codex-acp, copilot-acp, amp-acp, pi-acp), on both the legacy agent
loop and the state-machine path.
---
**To Reproduce**
Manual (ACP frontend, claude-acp provider, `goose_mode: smart_approve`):
1. Ask for a task involving several shell commands (each triggers a permission
round-trip).
2. Submit a new chat message while a permission request is in flight. A
permission handler that delays ~2s before answering makes the window easy to hit.
3. Session goes silent: new message produces nothing, no error surfaces, the
goose session file stops recording.
4. Recovery only when the abandoned child turn ends on its own. Post-recovery,
the agent's transcript shows repeated
`Tool permission request failed: Error: Internal error`.
Deterministic (integration-style, fixture at `crates/goose/tests/acp_fixtures/server.rs`):
1. Connect an `AcpProvider` to a fixture agent; start prompt A; fixture issues
`session/request_permission` and holds the turn open.
2. Drop the returned `MessageStream` (simulates cancellation), then call
`stream()` again (prompt B).
3. Fixture sends another `session/request_permission` plus a `tool_call` update
within turn A.
4. Observe: no `session/cancel` ever arrives; the permission request gets
-32603; the update vanishes; prompt B waits until the fixture ends turn A.
---
**Expected behavior**
- Cancelling/replacing a turn ends the child's turn promptly via `session/cancel`
(conforming ACP agents finish with `StopReason::Cancelled`).
- An unanswerable permission request resolves as a clean cancellation the model
can reason about, not an opaque internal error.
- A prompt submitted after cancellation dispatches in seconds, not "whenever the
abandoned turn ends."
- These paths log something — today the -32603 branch and the dropped updates
are entirely silent, which is what made diagnosis hard.
---
**Screenshots**
---
**Please provide the following information**
- **OS & Arch:** macOS arm64 `[confirm with reporter]`
- **Interface:** Neither goose UI nor CLI — goosed's ACP server over WebSocket,
driven by an internal desktop frontend (Berd). Bug is in goosed's ACP provider
layer; reproduces with any ACP client.
- **Version:** goose @ `b9b671c6` (pinned by the frontend). `crates/goose/src/acp/`
differs from current `main` by only 5 unrelated lines, so this reproduces on `main`.
- **Extensions enabled:** Not implicated — failure is below the extension layer.
`[exact list available from the session export if needed]`
- **Provider & Model:** claude-acp via `@agentclientprotocol/claude-agent-acp@0.66.0`
wrapping Claude Code; `goose_mode: smart_approve`. All ACP providers share the
affected path.
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.