Hmbown/CodeWhale · error
Inherited interactive terminal takeover is unavailable on…
Error message
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
What it means
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.
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.
Example fix
// before
{"command": "python", "interactive": true} // Unix takeover -> refused
// after
{"command": "python", "background": true, "tty": true} // then action:"interact", task_id Defensive patterns
Strategy: fallback
Validate before calling
#[cfg(unix)]
if wants_inherited_interactive {
return Err("use background:true + tty:true + interact on Unix".into());
} Type guard
fn inherited_interactive_supported() -> bool { cfg!(windows) } Try / catch
match shell.execute_interactive(cmd) {
Err(e) if e.to_string().contains("terminal takeover is unavailable") => {
// fall back to the sanctioned lane
let id = shell.spawn_background_tty(cmd)?;
shell.interact(&id, input, false)?;
}
other => other?,
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
- Automation lock must not have hard links
- Codewhale credentials directory must be owned by the…
- Codewhale-owned credential file must be singly linked…
- {message}
- PermissionDenied
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/b2a2567c4968d930.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/shell.rs:2135
pub fn execute_interactive_with_policy_env(
&mut self,
command: &str,
working_dir: Option<&str>,
timeout_ms: u64,
policy_override: Option<ExecutionSandboxPolicy>,
extra_env: HashMap<String, String>,
) -> Result<ShellResult> {
crate::shell_dispatcher::ShellDispatcher::log_exec(command);
// A new Unix process group that inherits the terminal is not its
// foreground owner. Letting it read stdin triggers SIGTTIN; sharing
// Codewhale's group instead would make cooked-mode Ctrl+C terminate
// both parent and child. Until this path owns a complete POSIX job-
// control lease, fail closed before spawning. Persistent PTY tools
// already provide a safe interactive lane without taking over the
// operator's live terminal.
if let Some(message) = inherited_interactive_terminal_refusal() {
return Err(anyhow!(message));
}
let work_dir = working_dir.map_or_else(|| self.default_workspace.clone(), PathBuf::from);
validate_shell_working_dir(&work_dir, working_dir.is_none())?;
let timeout_ms = timeout_ms.clamp(1000, 600_000);
let policy = policy_override.unwrap_or_else(|| self.sandbox_policy.clone());
let spec = CommandSpec::shell(command, work_dir.clone(), Duration::from_millis(timeout_ms))
.with_policy(policy)
.with_env(extra_env);
let exec_env = self.sandbox_manager.prepare(&spec);
Self::execute_interactive_sandboxed(command, &work_dir, timeout_ms, &exec_env)
}
/// Execute command synchronously with timeout (sandboxed).
fn execute_sync_sandboxed(View on GitHub (pinned to 433685b202)