Hmbown/CodeWhale · error

Codewhale TUI could not verify foreground terminal ownership

Error message

Codewhale TUI could not verify foreground terminal ownership: {}

What it means

require_foreground_terminal_owner calls tcgetpgrp(STDIN_FILENO) to learn which process group owns the controlling terminal. A negative return means the tcgetpgrp syscall itself failed (stdin is not a controlling terminal or was closed), and the error embeds the errno via io::Error::last_os_error(). The TUI refuses to start because it cannot verify it is safe to take over raw terminal mode.

Solutions

  1. Run the TUI in a real interactive terminal with stdin attached to a tty.
  2. Allocate a pty if in a sandbox/automation context: `script -qec "codew" /dev/null` or use a terminal multiplexer (tmux/screen).
  3. For non-interactive use, switch to `codewhale exec "…"` which does not require foreground ownership.
  4. Check that the fd 0 is not redirected or closed in the launch wrapper; fix the launcher script.

Example fix

// before
codew < input.txt
// after
codewhale exec "$(cat input.txt)"
Defensive patterns

Strategy: validation

Validate before calling

// Rust-ish check before launching the TUI
let is_tty = unsafe { libc::isatty(libc::STDIN_FILENO) == 1 };
if !is_tty { eprintln!("stdin is not a terminal; use codewhale exec"); }
let fg = unsafe { libc::tcgetpgrp(libc::STDIN_FILENO) };
if fg < 0 { /* tcgetpgrp failed; no controlling terminal */ }

Type guard

fn has_controlling_terminal() -> bool {
    unsafe { libc::tcgetpgrp(libc::STDIN_FILENO) >= 0 }
}

Try / catch

match require_foreground_terminal_owner() {
    Err(e) if e.to_string().contains("could not verify foreground terminal ownership") => {
        eprintln!("no controlling terminal; run in an interactive tty or use `codewhale exec`")
    }
    Err(e) => return Err(e),
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Running the TUI with stdin not attached to a terminal, e.g. stdin redirected from a file/pipe (`codew < file`), stdin closed (`codew 0<&-`), stdin connected to a pty whose controlling session has been revoked, or inside environments without a real controlling tty (some CI runners, detached daemons).

Common situations: Developers piping input into the TUI, running it under process supervisors or service managers that detach the controlling tty, or in CI sandboxes where no pty is allocated. Also appears when the tty is hung up mid-launch.

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/a9700f157b4f0ccc. Report an issue: GitHub.

Appendix: source

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

         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) =
        unsafe { (libc::tcgetpgrp(libc::STDIN_FILENO), libc::getpgrp()) };
    if terminal_pgid < 0 {
        return Err(anyhow::anyhow!(
            "Codewhale TUI could not verify foreground terminal ownership: {}",
            io::Error::last_os_error()
        ));
    }
    validate_foreground_process_group(terminal_pgid, process_pgid)
}

#[cfg(not(unix))]
pub(crate) fn require_foreground_terminal_owner() -> Result<()> {
    Ok(())
}

#[cfg(unix)]
pub(crate) fn validate_foreground_process_group(
    terminal_pgid: libc::pid_t,
    process_pgid: libc::pid_t,
) -> Result<()> {
    if terminal_pgid == process_pgid {

View on GitHub (pinned to 73e0f67d83)