From 4b7bbfe556fcf7c38797d181a6240c685bcaf898 Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Wed, 5 Nov 2025 12:18:55 -0500 Subject: [PATCH 1/2] Listen for ctrl-c during provider request --- crates/goose-cli/src/session/input.rs | 1 + crates/goose-cli/src/session/mod.rs | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index a1d44b877fda..34d385291f26 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -74,6 +74,7 @@ pub fn get_input( Ok(text) => text, Err(e) => match e { rustyline::error::ReadlineError::Interrupted => return Ok(InputResult::Exit), + rustyline::error::ReadlineError::Eof => return Ok(InputResult::Exit), _ => return Err(e.into()), }, }; diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 35f8999d01a2..37106de6a46c 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -775,8 +775,6 @@ impl CliSession { interactive: bool, cancel_token: CancellationToken, ) -> Result<()> { - let cancel_token_clone = cancel_token.clone(); - // Cache the output format check to avoid repeated string comparisons in the hot loop let is_json_mode = self.output_format == "json"; @@ -790,6 +788,14 @@ impl CliSession { .messages .last() .ok_or_else(|| anyhow::anyhow!("No user message"))?; + + let cancel_token_interrupt = cancel_token.clone(); + tokio::spawn(async move { + if tokio::signal::ctrl_c().await.is_ok() { + cancel_token_interrupt.cancel(); + } + }); + let mut stream = self .agent .reply( @@ -800,6 +806,7 @@ impl CliSession { .await?; let mut progress_bars = output::McpSpinners::new(); + let cancel_token_clone = cancel_token.clone(); use futures::StreamExt; loop { @@ -1075,8 +1082,7 @@ impl CliSession { None => break, } } - _ = tokio::signal::ctrl_c() => { - cancel_token_clone.cancel(); + _ = cancel_token_clone.cancelled() => { drop(stream); if let Err(e) = self.handle_interrupted_messages(true).await { eprintln!("Error handling interruption: {}", e); From 28b06667801c3acdac8b02d24caa20c0a6537a50 Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Wed, 5 Nov 2025 15:16:55 -0500 Subject: [PATCH 2/2] Abort the task --- Cargo.lock | 1 + crates/goose-cli/Cargo.toml | 2 +- crates/goose-cli/src/session/mod.rs | 7 +++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b84c46478ed8..02008bf05183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7055,6 +7055,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] diff --git a/crates/goose-cli/Cargo.toml b/crates/goose-cli/Cargo.toml index 83ebb8644a92..dbb546843778 100644 --- a/crates/goose-cli/Cargo.toml +++ b/crates/goose-cli/Cargo.toml @@ -54,7 +54,7 @@ tower-http = { version = "0.5", features = ["cors", "fs", "auth"] } http = "1.0" webbrowser = "1.0" indicatif = "0.17.11" -tokio-util = { version = "0.7.15", features = ["compat"] } +tokio-util = { version = "0.7.15", features = ["compat", "rt"] } is-terminal = "0.4.16" anstream = "0.6.18" url = "2.5.7" diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 37106de6a46c..5cc3dd18017c 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -13,6 +13,8 @@ use crate::session::task_execution_display::{ use goose::conversation::Conversation; use std::io::Write; use std::str::FromStr; +use tokio::signal::ctrl_c; +use tokio_util::task::AbortOnDropHandle; pub use self::export::message_to_markdown; pub use builder::{build_session, SessionBuilderConfig, SessionSettings}; @@ -790,11 +792,12 @@ impl CliSession { .ok_or_else(|| anyhow::anyhow!("No user message"))?; let cancel_token_interrupt = cancel_token.clone(); - tokio::spawn(async move { - if tokio::signal::ctrl_c().await.is_ok() { + let handle = tokio::spawn(async move { + if ctrl_c().await.is_ok() { cancel_token_interrupt.cancel(); } }); + let _drop_handle = AbortOnDropHandle::new(handle); let mut stream = self .agent