fix: prevent session hang when tool approval required in headless mode (#7915)
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -268,7 +268,7 @@ where
|
|||||||
let mut error = None;
|
let mut error = None;
|
||||||
for message in &messages {
|
for message in &messages {
|
||||||
if let Err(e) = cli_session
|
if let Err(e) = cli_session
|
||||||
.process_message(message.clone(), CancellationToken::default())
|
.process_message(message.clone(), CancellationToken::default(), false)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error = Some(e.to_string());
|
error = Some(e.to_string());
|
||||||
|
|||||||
@@ -488,10 +488,12 @@ impl CliSession {
|
|||||||
&mut self,
|
&mut self,
|
||||||
message: Message,
|
message: Message,
|
||||||
cancel_token: CancellationToken,
|
cancel_token: CancellationToken,
|
||||||
|
interactive: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let cancel_token = cancel_token.clone();
|
let cancel_token = cancel_token.clone();
|
||||||
self.push_message(message);
|
self.push_message(message);
|
||||||
self.process_agent_response(false, cancel_token).await?;
|
self.process_agent_response(interactive, cancel_token)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,7 +501,7 @@ impl CliSession {
|
|||||||
pub async fn interactive(&mut self, prompt: Option<String>) -> Result<()> {
|
pub async fn interactive(&mut self, prompt: Option<String>) -> Result<()> {
|
||||||
if let Some(prompt) = prompt {
|
if let Some(prompt) = prompt {
|
||||||
let msg = Message::user().with_text(&prompt);
|
let msg = Message::user().with_text(&prompt);
|
||||||
self.process_message(msg, CancellationToken::default())
|
self.process_message(msg, CancellationToken::default(), true)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,7 +957,7 @@ impl CliSession {
|
|||||||
/// Process a single message and exit
|
/// Process a single message and exit
|
||||||
pub async fn headless(&mut self, prompt: String) -> Result<()> {
|
pub async fn headless(&mut self, prompt: String) -> Result<()> {
|
||||||
let message = Message::user().with_text(&prompt);
|
let message = Message::user().with_text(&prompt);
|
||||||
self.process_message(message, CancellationToken::default())
|
self.process_message(message, CancellationToken::default(), false)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1009,7 +1011,28 @@ impl CliSession {
|
|||||||
match result {
|
match result {
|
||||||
Some(Ok(AgentEvent::Message(message))) => {
|
Some(Ok(AgentEvent::Message(message))) => {
|
||||||
if let Some((id, security_prompt)) = find_tool_confirmation(&message) {
|
if let Some((id, security_prompt)) = find_tool_confirmation(&message) {
|
||||||
let permission = prompt_tool_confirmation(&security_prompt)?;
|
let permission = if interactive {
|
||||||
|
prompt_tool_confirmation(&security_prompt)?
|
||||||
|
} else {
|
||||||
|
// Non-interactive/headless mode: refuse to run in
|
||||||
|
// Approve/SmartApprove modes since auto-allowing would
|
||||||
|
// bypass the safety contract those modes are meant to enforce.
|
||||||
|
let config = Config::global();
|
||||||
|
let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto);
|
||||||
|
if goose_mode == GooseMode::Approve || goose_mode == GooseMode::SmartApprove {
|
||||||
|
cancel_token_clone.cancel();
|
||||||
|
drop(stream);
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Tool approval required in non-interactive mode with GooseMode::{goose_mode}. \
|
||||||
|
This is an invalid configuration — Approve/SmartApprove modes require an \
|
||||||
|
interactive terminal. Use GooseMode::Auto for headless sessions."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tracing::warn!(
|
||||||
|
"Tool confirmation required in non-interactive mode, auto-allowing"
|
||||||
|
);
|
||||||
|
Permission::AllowOnce
|
||||||
|
};
|
||||||
|
|
||||||
if permission == Permission::Cancel {
|
if permission == Permission::Cancel {
|
||||||
output::render_text("Tool call cancelled. Returning to chat...", Some(Color::Yellow), true);
|
output::render_text("Tool call cancelled. Returning to chat...", Some(Color::Yellow), true);
|
||||||
@@ -1036,6 +1059,18 @@ impl CliSession {
|
|||||||
permission,
|
permission,
|
||||||
}).await;
|
}).await;
|
||||||
} else if let Some((elicitation_id, elicitation_message, schema)) = find_elicitation_request(&message) {
|
} else if let Some((elicitation_id, elicitation_message, schema)) = find_elicitation_request(&message) {
|
||||||
|
if !interactive {
|
||||||
|
// Non-interactive/headless mode: cannot collect user input
|
||||||
|
tracing::warn!(
|
||||||
|
"Elicitation requested in non-interactive mode, cancelling"
|
||||||
|
);
|
||||||
|
cancel_token_clone.cancel();
|
||||||
|
drop(stream);
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Elicitation requested but no interactive terminal is available to collect user input"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
output::hide_thinking();
|
output::hide_thinking();
|
||||||
let _ = progress_bars.hide();
|
let _ = progress_bars.hide();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user