{"record":{"id":"6875e62d99ecdca4","repo":"denisidoro/navi","slug":"invalid-utf8-output-from-stty","errorCode":null,"errorMessage":"Invalid utf8 output from stty","messagePattern":"Invalid utf8 output from stty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/common/terminal.rs","lineNumber":26,"sourceCode":"fn width_with_shell_out() -> Result<u16> {\n    let output = if cfg!(target_os = \"macos\") {\n        Command::new(\"stty\")\n            .arg(\"-f\")\n            .arg(\"/dev/stderr\")\n            .arg(\"size\")\n            .stderr(Stdio::inherit())\n            .output()?\n    } else {\n        Command::new(\"stty\")\n            .arg(\"size\")\n            .arg(\"-F\")\n            .arg(\"/dev/stderr\")\n            .stderr(Stdio::inherit())\n            .output()?\n    };\n\n    if let Some(0) = output.status.code() {\n        let stdout = String::from_utf8(output.stdout).expect(\"Invalid utf8 output from stty\");\n        let mut data = stdout.split_whitespace();\n        data.next();\n        return data\n            .next()\n            .expect(\"Not enough data\")\n            .parse::<u16>()\n            .map_err(|_| anyhow!(\"Invalid width\"));\n    }\n\n    Err(anyhow!(\"Invalid status code\"))\n}\n\npub fn width() -> u16 {\n    if let Ok((w, _)) = terminal::size() {\n        w\n    } else {\n        width_with_shell_out().unwrap_or(FALLBACK_WIDTH)\n    }","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/denisidoro/navi/blob/f7330b9ad5bd95b7d1a3c96d00e0a77deb589147/src/common/terminal.rs#L8-L44","documentation":"`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.","triggerScenarios":"`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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet stdout = String::from_utf8(output.stdout).expect(\"Invalid utf8 output from stty\");\n// after\nlet stdout = String::from_utf8_lossy(&output.stdout).into_owned();","handlingStrategy":"fallback","validationCode":"let out = std::process::Command::new(\"stty\")\n    .arg(\"size\").stdin(std::process::Stdio::inherit())\n    .output()?;\nif out.status.success() && std::str::from_utf8(&out.stdout)\n    .map(|s| s.split_whitespace().count() >= 2)\n    .unwrap_or(false) { /* safe to parse */ }","typeGuard":"fn valid_stty_output(bytes: &[u8]) -> bool {\n    std::str::from_utf8(bytes)\n        .ok()\n        .map(|s| {\n            let mut it = s.split_whitespace();\n            it.next().and_then(|a| a.parse::<u16>().ok()).is_some()\n                && it.next().and_then(|b| b.parse::<u16>().ok()).is_some()\n        })\n        .unwrap_or(false)\n}","tryCatchPattern":"// expect panics can't be caught as Err; detect the condition and fall back:\nlet width = if valid_stty_output(&out.stdout) {\n    parse_width(&String::from_utf8_lossy(&out.stdout))\n} else {\n    fallback_width() // e.g. tput cols or terminal_size crate, else 80\n};","preventionTips":["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"],"tags":["rust","panic","terminal","stty","encoding"],"backgroundTag":"non-utf8-command-output","analyzedSha":"f7330b9ad5bd95b7d1a3c96d00e0a77deb589147","analyzedAt":"2026-09-03T13:58:22.429Z","contentChangedAt":"2026-09-03T13:58:22.429Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}