nushell/nushell · error · std::io::Error

{context}: {err}

Error message

{context}: {err}

What it means

`input list` runs a crossterm-based interactive selector that must enable raw mode, hide the cursor, query terminal size, render frames, and poll/read terminal events. `io_context` decorates any io::Error from those steps with a static label ('enable raw mode', 'hide terminal cursor', 'poll terminal event', ...) while preserving the original ErrorKind, so failures surface as '<step>: <cause>'.

Source

Thrown at crates/nu-command/src/platform/input/list.rs:89

// - STREAM_PREFETCH_MARGIN: how far from the end we begin prefetching.
// - STREAM_CHANNEL_CAPACITY: rows the background reader can collect before the UI drains them.
// - STREAM_POLL_INTERVAL: render cadence while a stream is still loading.
// - STREAM_FOOTER_UPDATE_INTERVAL: visible footer animation/count cadence while rows stream in.
const INITIAL_STREAM_COLLECT_TIMEOUT: Duration = Duration::from_millis(250);
const INITIAL_STREAM_MAX_ITEMS: usize = 100_000;
const STREAM_LOAD_BATCH: usize = 512;
const STREAM_PREFETCH_MARGIN: usize = 2;
const STREAM_CHANNEL_CAPACITY: usize = 8192;
const STREAM_SPINNER_FRAMES: &[&str] = &["-", "\\", "|", "/"];
const STREAM_DRAIN_TIME_BUDGET: Duration = Duration::from_millis(16);
const STREAM_POLL_INTERVAL: Duration = Duration::from_millis(16);
const STREAM_FOOTER_UPDATE_INTERVAL: Duration = Duration::from_millis(125);
const IDLE_POLL_INTERVAL: Duration = Duration::from_millis(100);
const FUZZY_FILTER_INTERRUPT_CHECK_INTERVAL: usize = 1024;
const FUZZY_FILTER_MIN_INTERRUPT_TIME: Duration = Duration::from_millis(16);

fn io_context(context: &'static str) -> impl FnOnce(io::Error) -> io::Error {
    move |err| io::Error::new(err.kind(), format!("{context}: {err}"))
}

fn terminal_char_width(c: char, current_column: usize) -> usize {
    match c {
        '\t' => {
            let next_tab_stop = ((current_column / 8) + 1) * 8;
            next_tab_stop - current_column
        }
        c if c.is_control() => 0,
        c => UnicodeWidthChar::width(c).unwrap_or(0),
    }
}

fn terminal_text_width_from(text: &str, start_column: usize) -> usize {
    let mut current_column = start_column;
    let mut chars = text.chars().peekable();

    while let Some(c) = chars.next() {

View on GitHub (pinned to 38769348f4)

Solutions

  1. Run the script in a real terminal or provide a PTY (`docker -it`, expect, ssh -t).
  2. Branch on interactivity: `if (term size).columns > 0 { input list ... } else { 'default' }`.
  3. For scheduled runs, replace `input list` with flags, arguments, or env-var defaults.

Example fix

# before
let choice = (input list --prompt pick: [a b])  # in CI: 'enable raw mode: ...'

# after
let choice = if ((term size).columns > 0) { input list --prompt pick: [a b] } else { 'a' }
Defensive patterns

Strategy: validation

Validate before calling

# Nushell: only prompt when a terminal is attached
let interactive = (try { term size; true } catch { false })
let choice = if $interactive { input list [a b] } else { 'default' }

Try / catch

try { input list [a b] } catch { |err| print ($err.msg); 'default' }

Prevention

When it happens

Trigger: Running `input list ...` when stdin/stdout is not a TTY (piped script, CI job, redirected output) so raw mode cannot be enabled; reading terminal size where none is reported; event read after the terminal closed.

Common situations: Scripts using `input list` executed non-interactively (cron, CI, subprocess pipes); running nu in environments without a real or emulated terminal; terminal multiplexers dropping the tty.

Related errors


AI-assisted analysis of nushell/nushell@38769348f4 (2026-08-16). Data as JSON: /api/errors/186e4b1d02639809. Report an issue: GitHub.