denisidoro/navi · warning
Invalid utf8 output from stty
Error message
Invalid utf8 output from stty
What it means
`terminal::width_with_shell_out` determines the terminal width by shelling out to `stty size` (or equivalent) and reading its stdout. When the child exits with code 0, the raw bytes are converted with `String::from_utf8(...).expect(...)`, which panics with "Invalid utf8 output from stty" if `stty` emitted non-UTF-8 bytes.
Source
Thrown at src/common/terminal.rs:26
fn width_with_shell_out() -> Result<u16> {
let output = if cfg!(target_os = "macos") {
Command::new("stty")
.arg("-f")
.arg("/dev/stderr")
.arg("size")
.stderr(Stdio::inherit())
.output()?
} else {
Command::new("stty")
.arg("size")
.arg("-F")
.arg("/dev/stderr")
.stderr(Stdio::inherit())
.output()?
};
if let Some(0) = output.status.code() {
let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
let mut data = stdout.split_whitespace();
data.next();
return data
.next()
.expect("Not enough data")
.parse::<u16>()
.map_err(|_| anyhow!("Invalid width"));
}
Err(anyhow!("Invalid status code"))
}
pub fn width() -> u16 {
if let Ok((w, _)) = terminal::size() {
w
} else {
width_with_shell_out().unwrap_or(FALLBACK_WIDTH)
}View on GitHub (pinned to f7330b9ad5)
Solutions
- Check what `stty` resolves to (`which stty`, `type stty`) and remove any alias/shadowing wrapper
- Run the tool in a clean shell (CI container) to rule out environment-injected output
- Fall back to `tput cols` or library-based width detection (e.g. `terminal_size` crate) instead of parsing `stty` output
- Patch to `String::from_utf8_lossy(&output.stdout)` so bad bytes don't panic
Example fix
// before
let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
// after
let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); Defensive patterns
Strategy: fallback
Validate before calling
let out = std::process::Command::new("stty")
.arg("size").stdin(std::process::Stdio::inherit())
.output()?;
if out.status.success() && std::str::from_utf8(&out.stdout)
.map(|s| s.split_whitespace().count() >= 2)
.unwrap_or(false) { /* safe to parse */ } Type guard
fn valid_stty_output(bytes: &[u8]) -> bool {
std::str::from_utf8(bytes)
.ok()
.map(|s| {
let mut it = s.split_whitespace();
it.next().and_then(|a| a.parse::<u16>().ok()).is_some()
&& it.next().and_then(|b| b.parse::<u16>().ok()).is_some()
})
.unwrap_or(false)
} Try / catch
// expect panics can't be caught as Err; detect the condition and fall back:
let width = if valid_stty_output(&out.stdout) {
parse_width(&String::from_utf8_lossy(&out.stdout))
} else {
fallback_width() // e.g. tput cols or terminal_size crate, else 80
}; Prevention
- Remove shell aliases/wrappers around `stty` that could inject extra output
- Use crate-based width detection (terminal_size/crossterm) instead of parsing stty
- Provide a sane default (80 columns) when terminal detection fails
- Test in clean CI shells where locale/env quirks are minimal
When it happens
Trigger: `stty` (or the platform-specific command) exits 0 but writes non-UTF-8 bytes to stdout — unusual locale/encoding, or a shell alias/wrapper around `stty` injecting binary output.
Common situations: Aliased or wrapped `stty` commands (e.g. a script printing extra ANSI/binary data); exotic locale environments on CI containers; a `stty` shim from a different toolchain on PATH.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Not enough data
- No variables received from finder
- Unable to process value
- Unable to get selection
- Unable to get query
AI-assisted analysis of denisidoro/navi@f7330b9ad5 (2026-09-03).
Data as JSON: /api/errors/6875e62d99ecdca4.
Report an issue: GitHub.