Hmbown/CodeWhale · error

Codewhale TUI requires an interactive terminal (stdin and…

Error message

Codewhale TUI requires an interactive terminal (stdin and stdout must be a TTY).
Open a real terminal (Terminal.app, iTerm, Windows Terminal, …) and run `codew` or `codewhale` there — not from a pipe, cron job, or non-TTY launcher.
For headless prompts use `codewhale exec "…"` instead.

What it means

Launch preflight error from require_interactive_terminal: the Codewhale TUI was started when either stdin or stdout is not a TTY, so an interactive full-screen UI cannot run. The message explains the environment problem and points to `codewhale exec "…"` for headless/non-interactive use. The TTY check is deliberately independent of std::io so the launch contract stays testable.

Solutions

  1. Run the command in a real interactive terminal (Terminal.app, iTerm, Windows Terminal) with no pipes or redirections on stdin/stdout.
  2. For SSH, attach a TTY: `ssh -t host codew`.
  3. For non-interactive/headless use, switch to `codewhale exec "…"` which is designed for pipes and scripts.
  4. In IDEs or launchers, configure the task to run in an integrated terminal rather than capturing output.

Example fix

# before (fails: stdout piped)
codew | tee session.log
# after
codew            # interactive TUI in a real terminal
codewhale exec "summarize the diff" > session.log   # headless alternative
Defensive patterns

Strategy: validation

Validate before calling

// check before launching the TUI
fn tui_can_run() -> bool {
    std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
}
if !tui_can_run() {
    eprintln!("use `codewhale exec \"…\"` for headless runs");
    std::process::exit(2);
}

Try / catch

match run_tui().await {
    Err(e) if e.to_string().contains("interactive terminal") => {
        eprintln!("{e:#}"); // print guidance and exit non-zero
        std::process::exit(2);
    }
    Err(e) => return Err(e),
    Ok(()) => {},
}

Prevention

When it happens

Trigger: Running `codew`/`codewhale` (run_tui -> require_interactive_terminal) with stdin_is_tty == false or stdout_is_tty == false — piping input/output, running under cron or a service manager, launching from an IDE task runner or CI, or redirecting stdout to a file.

Common situations: `codew < file` or `codew | tee log`; cron/systemd jobs; IDE run-configurations that capture output; SSH without `-t`; containers or exec wrappers without a pseudo-terminal.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/71ce44201494de58. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/ui/terminal.rs:107

}

fn observe_terminal_attention(event: &Event) {
    match event {
        Event::FocusGained => crate::tui::notifications::set_terminal_focused(true),
        Event::FocusLost => crate::tui::notifications::set_terminal_focused(false),
        _ => {}
    }
}

/// Refuse to enter raw mode unless both interactive streams are TTYs.
///
/// Keeping this check independent from `std::io` makes the launch contract
/// testable without trying to manipulate the test runner's own terminal.
pub(crate) fn require_interactive_terminal(stdin_is_tty: bool, stdout_is_tty: bool) -> Result<()> {
    if stdin_is_tty && stdout_is_tty {
        return Ok(());
    }
    Err(anyhow::anyhow!(
        "Codewhale TUI requires an interactive terminal (stdin and stdout must be a TTY).\n\
         Open a real terminal (Terminal.app, iTerm, Windows Terminal, …) and run `codew` \
         or `codewhale` there — not from a pipe, cron job, or non-TTY launcher.\n\
         For headless prompts use `codewhale exec \"…\"` instead."
    ))
}

/// Refuse to enter terminal modes from a background Unix process group.
///
/// A TTY can still report `isatty(3) == true` after a shell has suspended the
/// process. Reading from that background group triggers `SIGTTIN`; enabling
/// mouse or keyboard protocols before that stop poisons the shell with raw
/// escape reports. Check foreground ownership before the first mode change.
#[cfg(unix)]
pub(crate) fn require_foreground_terminal_owner() -> Result<()> {
    // SAFETY: both calls are read-only process/terminal queries on the
    // controlling stdin descriptor and require no borrowed memory.
    let (terminal_pgid, process_pgid) =

View on GitHub (pinned to 73e0f67d83)