aaif-goose / aaif-goose/goose

ACP provider: cancelling a prompt doesn't interrupt the agent; UI stays locked until the turn finishes

Open
#10,619 5 comments 0 reactions 1 assignee Claimed by @lifeizhou-ap View on GitHub
Dominant language
Rust
Stars
54.2k
Forks
6.2k
Avg merge
3d 4h
Merged PRs (30d)
240

Description

## Summary

When goose uses an ACP agent as its provider (e.g. Claude Code via the `claude_acp` provider), cancelling a prompt does not interrupt the agent. The session stays in the "running" state — the desktop **send button stays disabled** and you cannot even queue a message — until the agent finishes the entire turn on its own, which can take many minutes.

## Root cause

Two independent gaps in the ACP client provider (`crates/goose/src/acp/provider.rs`) and the agent reply loop (`crates/goose/src/agents/agent.rs`) combine:

**1. goose never sends `session/cancel` to the ACP agent.** The client→agent request channel has no cancel path:

```rust
enum ClientRequest { // provider.rs:65
NewSession { … }
SetMode { … }
SetConfigOption { … }
Prompt { … }
}
```

There is no `Cancel` variant and nowhere does the client send a `session/cancel` notification. (goose only *handles* incoming cancels when it is itself the ACP server, in `server.rs`.) `Provider::stream()` (provider.rs:472) does not even take a cancellation token. So when the user cancels, goose sets its own cancel token but the agent is never told to stop and keeps running the whole turn.

**2. goose's cancellation is cooperative and the check can't fire while the stream is blocked.** The reply loop only checks the cancel token *after* a stream item arrives:

```rust
while let Some(next) = stream.next().await { // agent.rs:2047
if is_token_cancelled(&cancel_token) || exit_chat { // agent.rs:2048
break;
}
```

There is no `tokio::select!` racing the token against the stream. The ACP provider stream yields only when the agent emits an update:

```rust
while let Some(update) = rx.recv().await { … } // provider.rs:522
```

and it only ends when `response_tx` is dropped — which happens after `block_task().await` on the outstanding `PromptRequest` completes (provider.rs:1168), i.e. only when the agent fully finishes the turn.

### Resulting chain

1. User cancels → cancel token set, but no `session/cancel` sent → agent keeps running.
2. While the agent is mid-turn with no output (slow tool call, thinking), `rx.recv().await` pends → `stream.next().await` pends.
3. The loop never reaches the `is_token_cancelled` check at agent.rs:2048, so the set token is never observed.
4. The `reply` future stays alive → session stays "running" → desktop keeps **send disabled**.
5. Only when the agent finishes on its own does the stream end and the UI free up.

## Repro

Use Claude Code as an ACP provider, send a prompt that triggers a long agentic turn, then cancel. The send button remains disabled for the remainder of the agent's turn.

Contributor guide

Open the contributing guide

Research direction

The issue points to `crates/goose/src/acp/provider.rs` and `crates/goose/src/agents/agent.rs`. Start by examining the `ClientRequest` enum and `Provider::stream()` to add a cancel path. Then, modify the reply loop to use `tokio::select!` to race the cancellation token against the stream. Testing involves using an ACP provider like Claude Code and verifying that cancelling a prompt immediately frees the UI.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
ai-infra-agents, backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.