svenstaro/genact · error

Couldn't get terminal width. Is an interactive terminal…

Error message

Couldn't get terminal width. Is an interactive terminal attached?

What it means

get_terminal_width() queries the terminal size via terminal_size::terminal_size(), and panics when it cannot determine a width. The function assumes a TTY (or on wasm32, a fake window.xterm) is attached; when stdout/stderr is not an interactive terminal (no ioctl TIOCGWINSZ data), the library deliberately panics rather than return a guessed width. It is called from run() to lay out progress/output formatting.

Solutions

  1. Run the program in an interactive terminal (attach a TTY: docker run -t, ssh -t, script -qec '...')
  2. Redirect output only for files while keeping stderr on a TTY, or use a pty wrapper (e.g. `script -c 'myapp'`)
  3. Patch/wrap get_terminal_width to fall back to a default width (e.g. 80) instead of panicking when no TTY is present
  4. Skip interactive rendering paths in run() when !atty/is-terminal detects a non-TTY stdout

Example fix

// before
pub fn get_terminal_width() -> usize {
    if let Some((width, _)) = terminal_size::terminal_size() {
        width.0.into()
    } else {
        panic!("Couldn't get terminal width. Is an interactive terminal attached?")
    }
}
// after
pub fn get_terminal_width() -> usize {
    terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80)
}
Defensive patterns

Strategy: fallback

Validate before calling

let has_tty = std::io::IsTerminal::is_terminal(&std::io::stdout());
let width: usize = if has_tty {
    terminal_size::terminal_size().map(|(w, _)| w.0 as usize).unwrap_or(80)
} else {
    80
};

Type guard

fn terminal_available() -> bool {
    use std::io::IsTerminal;
    std::io::stdout().is_terminal() && terminal_size::terminal_size().is_some()
}

Try / catch

// Rust panics cannot be caught with try/catch; either pre-check with terminal_available()
// or catch the unwind:
let width = std::panic::catch_unwind(get_terminal_width)
    .unwrap_or(80);

Prevention

When it happens

Trigger: Calling run() (which calls get_terminal_width()) in an environment where terminal_size::terminal_size() returns None: piping output to a file or another program, running under CI (no TTY), running in a non-interactive ssh/exec context, or a WSL/dumb terminal that does not report size.

Common situations: CI pipelines (GitHub Actions, GitLab CI) where stdout is a pipe; `cargo run | tee log.txt`; docker run without -t; background jobs or systemd services with no controlling terminal; IDE run consoles that are not real PTYs.

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.


AI-assisted analysis of svenstaro/genact@858be63ad2 (2026-09-08). Data as JSON: /api/errors/63c078f1479b98d0. Report an issue: GitHub.

Appendix: source

Thrown at src/io.rs:117

pub async fn cursor_up(n: u64) {
    print(format!("\x1b[{n}A")).await;
}

// pub async fn cursor_left(n: u64) {
//     print(format!("\x1b[{}D", n)).await;
// }

pub async fn erase_line() {
    print("\x1b[2K\x1b[0G").await;
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_terminal_width() -> usize {
    if let Some((width, _)) = terminal_size::terminal_size() {
        width.0.into()
    } else {
        panic!("Couldn't get terminal width. Is an interactive terminal attached?")
    }
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(inline_js = "export function get_terminal_width() { return window.xterm.cols }")]
extern "C" {
    pub fn get_terminal_width() -> usize;
}

View on GitHub (pinned to 858be63ad2)