aaif-goose/goose · error
Tool approval required in non-interactive mode with GooseMod
Error message
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. What it means
When a tool needs confirmation and the session is not interactive (piped stdin/stdout, headless run), goose refuses to auto-approve while GooseMode is Approve or SmartApprove, because silently allowing would break the safety contract those modes promise. The stream is cancelled, the token dropped, and this error returned; {goose_mode} interpolates the offending mode name.
Source
Thrown at crates/goose-cli/src/session/mod.rs:1489
result = stream.next() => {
match result {
Some(Ok(AgentEvent::Message(message))) => {
if first_token_at.is_none() && message_has_text(&message) {
first_token_at = Some(Instant::now());
}
if let Some((id, security_prompt)) = find_tool_confirmation(&message) {
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 {
output::render_text("Tool call cancelled. Returning to chat...", Some(Color::Yellow), true);
self.agent.handle_confirmation(id.clone(), PermissionConfirmation {
principal_type: PrincipalType::Tool,
permission: Permission::DenyOnce,
}).await;
let mut response_message = Message::user();View on GitHub (pinned to 3810898a74)
Solutions
- Set GOOSE_MODE=auto (or remove the mode override) for headless runs
- Run the session in an interactive terminal when Approve/SmartApprove is intended
- Disable approval-requiring extensions in headless profiles so nothing requests confirmation
Example fix
# before GOOSE_MODE=approve goose run -i 'clean the repo' # after GOOSE_MODE=auto goose run -i 'clean the repo'
Defensive patterns
Strategy: validation
Validate before calling
let mode = std::env::var("GOOSE_MODE").unwrap_or_else(|_| "auto".into());
if !interactive && (mode == "approve" || mode == "smart_approve") {
panic!("headless sessions cannot use {} mode; set GOOSE_MODE=auto", mode);
} Try / catch
match run_headless().await {
Err(e) if e.to_string().contains("non-interactive mode") => {
eprintln!("rerun with GOOSE_MODE=auto or an interactive terminal");
std::process::exit(2);
}
other => other,
} Prevention
- Export GOOSE_MODE=auto in scripts, CI, and cron before invoking goose
- Never persist approve/smart_approve in configs used by headless jobs
- Audit enabled extensions for confirmation-requiring tools before automating
When it happens
Trigger: goose execute / goose run / piped-stdin sessions with GOOSE_MODE=approve (or smart_approve persisted in config) while any enabled extension requests tool confirmation during the reply loop.
Common situations: CI or cron scripts reusing an interactive-era config; GOOSE_MODE left exported in the shell; scheduled jobs expecting Auto behavior.
Related errors
- GOOSE_SERVER__SECRET_KEY is required for goose serve
- GOOSE_SERVER__SECRET_KEY must be set when using GOOSE_EXTERN
- no text provided for prompt in headless mode
- Failed to list recipes: {}
- No command provided in extension string
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/6379b487fb70f452.
Report an issue: GitHub.