{"record":{"id":"6379b487fb70f452","repo":"aaif-goose/goose","slug":"tool-approval-required-in-non-interactive-mode-wit","errorCode":null,"errorMessage":"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.","messagePattern":"Tool approval required in non-interactive mode with GooseMode::(.+?)\\. This is an invalid configuration — Approve/SmartApprove modes require an interactive terminal\\. Use GooseMode::Auto for headless sessions\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-cli/src/session/mod.rs","lineNumber":1489,"sourceCode":"                result = stream.next() => {\n                    match result {\n                        Some(Ok(AgentEvent::Message(message))) => {\n                            if first_token_at.is_none() && message_has_text(&message) {\n                                first_token_at = Some(Instant::now());\n                            }\n                            if let Some((id, security_prompt)) = find_tool_confirmation(&message) {\n                                let permission = if interactive {\n                                    prompt_tool_confirmation(&security_prompt)?\n                                } else {\n                                    // Non-interactive/headless mode: refuse to run in\n                                    // Approve/SmartApprove modes since auto-allowing would\n                                    // bypass the safety contract those modes are meant to enforce.\n                                    let config = Config::global();\n                                    let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto);\n                                    if goose_mode == GooseMode::Approve || goose_mode == GooseMode::SmartApprove {\n                                        cancel_token_clone.cancel();\n                                        drop(stream);\n                                        return Err(anyhow::anyhow!(\n                                            \"Tool approval required in non-interactive mode with GooseMode::{goose_mode}. \\\n                                             This is an invalid configuration — Approve/SmartApprove modes require an \\\n                                             interactive terminal. Use GooseMode::Auto for headless sessions.\"\n                                        ));\n                                    }\n                                    tracing::warn!(\n                                        \"Tool confirmation required in non-interactive mode, auto-allowing\"\n                                    );\n                                    Permission::AllowOnce\n                                };\n\n                                if permission == Permission::Cancel {\n                                    output::render_text(\"Tool call cancelled. Returning to chat...\", Some(Color::Yellow), true);\n                                    self.agent.handle_confirmation(id.clone(), PermissionConfirmation {\n                                        principal_type: PrincipalType::Tool,\n                                        permission: Permission::DenyOnce,\n                                    }).await;\n                                    let mut response_message = Message::user();","sourceCodeStart":1471,"sourceCodeEnd":1507,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-cli/src/session/mod.rs#L1471-L1507","documentation":"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.","triggerScenarios":"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.","commonSituations":"CI or cron scripts reusing an interactive-era config; GOOSE_MODE left exported in the shell; scheduled jobs expecting Auto behavior.","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"],"exampleFix":"# before\nGOOSE_MODE=approve goose run -i 'clean the repo'\n# after\nGOOSE_MODE=auto goose run -i 'clean the repo'","handlingStrategy":"validation","validationCode":"let mode = std::env::var(\"GOOSE_MODE\").unwrap_or_else(|_| \"auto\".into());\nif !interactive && (mode == \"approve\" || mode == \"smart_approve\") {\n    panic!(\"headless sessions cannot use {} mode; set GOOSE_MODE=auto\", mode);\n}","typeGuard":null,"tryCatchPattern":"match run_headless().await {\n    Err(e) if e.to_string().contains(\"non-interactive mode\") => {\n        eprintln!(\"rerun with GOOSE_MODE=auto or an interactive terminal\");\n        std::process::exit(2);\n    }\n    other => other,\n}","preventionTips":["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"],"tags":["permissions","headless","config","safety"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}