{"record":{"id":"6314d552dab2caed","repo":"warpdotdev/warp","slug":"refusing-to-delete-runner-uid-without-confirma","errorCode":null,"errorMessage":"Refusing to delete runner '{uid}' without confirmation in non-interactive mode (use --force to bypass)","messagePattern":"Refusing to delete runner '(.+?)' without confirmation in non-interactive mode \\(use --force to bypass\\)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"app/src/ai/agent_sdk/runner.rs","lineNumber":213,"sourceCode":"            },\n            |_, result: Result<()>, ctx| finish_command(result, ctx),\n        );\n    }\n}\n\nimpl warpui::Entity for RunnerCommandRunner {\n    type Event = ();\n}\nimpl SingletonEntity for RunnerCommandRunner {}\n\n/// Prompt the user to confirm deletion of a runner.\n///\n/// Returns `Ok(true)`/`Ok(false)` for an interactive confirm/decline. In\n/// non-interactive mode (no TTY) without `--force`, returns `Err` so the caller\n/// fails loudly (non-zero exit) instead of silently skipping the delete.\nfn confirm_delete(uid: &str, is_terminal: bool) -> Result<bool> {\n    if !is_terminal {\n        return Err(anyhow!(\n            \"Refusing to delete runner '{uid}' without confirmation in non-interactive mode (use --force to bypass)\"\n        ));\n    }\n\n    Ok(inquire::Confirm::new(&format!(\"Delete runner '{uid}'?\"))\n        .with_default(false)\n        .prompt()\n        .unwrap_or_default())\n}\n\n/// Resolve a runner by UID or (unambiguous) name from a fetched list.\nfn resolve_runner<'a>(\n    runners: &'a [Runner],\n    id: Option<&str>,\n    name: Option<&str>,\n) -> Result<&'a Runner> {\n    if let Some(id) = id {\n        return runners","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/warpdotdev/warp/blob/e72fd7aacbbb2236d9b3be2aad7e7178fe94b4bc/app/src/ai/agent_sdk/runner.rs#L195-L231","documentation":"A deliberate safety guard in runner deletion: without a TTY there is no way to show the confirm prompt, and silently skipping the delete would mislead scripts into thinking it happened. Non-interactive runs without --force therefore fail loudly with a non-zero exit so callers notice.","triggerScenarios":"`warp runner delete <uid>` executed where stdin/stdout is not a terminal (CI job, cron, piped script) and --force was not passed, so confirm_delete gets is_terminal=false.","commonSituations":"CI cleanup jobs; scripts developed interactively (where the prompt worked) later run headless; output piped through another command, making is_terminal false.","solutions":["Add --force to the delete command in scripts and CI","Run the command in an interactive terminal to get the y/N prompt","In wrappers, detect non-TTY and require explicit operator confirmation before delegating"],"exampleFix":"# before\nwarp runner delete rnr_123   # inside CI: refuses, non-zero exit\n\n# after\nwarp runner delete rnr_123 --force","handlingStrategy":"validation","validationCode":"# Decide interactiveness up front and pass explicit intent\nif [ -t 0 ]; then\n  warp runner delete \"$uid\"\nelse\n  warp runner delete \"$uid\" --force\nfi","typeGuard":null,"tryCatchPattern":"out=$(warp runner delete \"$uid\" 2>&1) || { case \"$out\" in *'without confirmation in non-interactive mode'*) echo 're-run with --force in scripts' >&2; exit 1;; *) echo \"$out\" >&2; exit 1;; esac; }","preventionTips":["Always pass --force in CI/cron delete commands","Test scripts in the same (non-TTY) context they will run in","Note that piping output makes the session non-interactive even in a terminal"],"tags":["runner","safety","interactive","cli"],"backgroundTag":null,"analyzedSha":"e72fd7aacbbb2236d9b3be2aad7e7178fe94b4bc","analyzedAt":"2026-08-16T08:27:25.381Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}