{"record":{"id":"b2a2567c4968d930","repo":"Hmbown/CodeWhale","slug":"inherited-interactive-terminal-takeover-is-unavailable-on","errorCode":null,"errorMessage":"Inherited interactive terminal takeover is unavailable on Unix because foreground TTY ownership cannot be transferred safely. Use Bash with `background: true, tty: true`, then continue it with `action: \"interact\"` and the returned `task_id`; alternatively","messagePattern":"Inherited interactive terminal takeover is unavailable on Unix because foreground TTY ownership cannot be transferred safely\\. Use Bash with `background: true, tty: true`, then continue it with `action: \"interact\"` and the returned `task_id`; alternatively","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/shell.rs","lineNumber":2135,"sourceCode":"    pub fn execute_interactive_with_policy_env(\n        &mut self,\n        command: &str,\n        working_dir: Option<&str>,\n        timeout_ms: u64,\n        policy_override: Option<ExecutionSandboxPolicy>,\n        extra_env: HashMap<String, String>,\n    ) -> Result<ShellResult> {\n        crate::shell_dispatcher::ShellDispatcher::log_exec(command);\n\n        // A new Unix process group that inherits the terminal is not its\n        // foreground owner. Letting it read stdin triggers SIGTTIN; sharing\n        // Codewhale's group instead would make cooked-mode Ctrl+C terminate\n        // both parent and child. Until this path owns a complete POSIX job-\n        // control lease, fail closed before spawning. Persistent PTY tools\n        // already provide a safe interactive lane without taking over the\n        // operator's live terminal.\n        if let Some(message) = inherited_interactive_terminal_refusal() {\n            return Err(anyhow!(message));\n        }\n\n        let work_dir = working_dir.map_or_else(|| self.default_workspace.clone(), PathBuf::from);\n        validate_shell_working_dir(&work_dir, working_dir.is_none())?;\n\n        let timeout_ms = timeout_ms.clamp(1000, 600_000);\n        let policy = policy_override.unwrap_or_else(|| self.sandbox_policy.clone());\n\n        let spec = CommandSpec::shell(command, work_dir.clone(), Duration::from_millis(timeout_ms))\n            .with_policy(policy)\n            .with_env(extra_env);\n        let exec_env = self.sandbox_manager.prepare(&spec);\n\n        Self::execute_interactive_sandboxed(command, &work_dir, timeout_ms, &exec_env)\n    }\n\n    /// Execute command synchronously with timeout (sandboxed).\n    fn execute_sync_sandboxed(","sourceCodeStart":2117,"sourceCodeEnd":2153,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/tools/shell.rs#L2117-L2153","documentation":"On Unix, the inherited-interactive mode (taking over the caller's foreground terminal for a child) is refused before spawning. POSIX cannot safely transfer foreground TTY ownership here: putting the child in Codewhale's process group would make cooked-mode Ctrl+C terminate both parent and child. The code fails closed until a full POSIX job-control lease exists; persistent PTY tools are the sanctioned interactive lane.","triggerScenarios":"Calling execute_interactive (inherited interactive terminal path) on Unix without a PTY/persistent-interactive lane — i.e. requesting terminal takeover of the live operator TTY.","commonSituations":"Trying to run an interactive program (vim, top, a REPL) by hijacking the agent's own terminal on Linux/macOS; porting a Windows interactive flow to Unix.","solutions":["Use Bash with `background: true, tty: true`, then continue it with `action: \"interact\"` and the returned `task_id` (the message's own recommendation).","Use a persistent PTY tool for interactive sessions instead of terminal takeover.","Rewrite the task as non-interactive (flags, stdin data, or config files) and run it synchronously.","On Windows this path may exist; guard interactive calls per-OS or accept the Unix refusal as intended fail-closed behavior."],"exampleFix":"// before\n{\"command\": \"python\", \"interactive\": true}          // Unix takeover -> refused\n// after\n{\"command\": \"python\", \"background\": true, \"tty\": true} // then action:\"interact\", task_id","handlingStrategy":"fallback","validationCode":"#[cfg(unix)]\nif wants_inherited_interactive {\n    return Err(\"use background:true + tty:true + interact on Unix\".into());\n}","typeGuard":"fn inherited_interactive_supported() -> bool { cfg!(windows) }","tryCatchPattern":"match shell.execute_interactive(cmd) {\n    Err(e) if e.to_string().contains(\"terminal takeover is unavailable\") => {\n        // fall back to the sanctioned lane\n        let id = shell.spawn_background_tty(cmd)?;\n        shell.interact(&id, input, false)?;\n    }\n    other => other?,\n}","preventionTips":["On Unix, never request foreground terminal takeover; use the background TTY + interact lane.","Design interactive flows around persistent PTY tools from the start.","Make interactive steps non-interactive (flags/stdin/config) where possible.","Read the fail-closed comment in shell.rs — this refusal is intentional, not a bug."],"tags":["unix","tty","security","interactive"],"backgroundTag":"unsupported-platform","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}