jdx/mise · error
truncated {label} text: {} bytes is not a whole number of co
Error message
truncated {label} text: {} bytes is not a whole number of code units What it means
from_utf16 decodes UTF-16 (LE or BE) text selected by a BOM, which must contain a whole number of 2-byte code units. mise throws this when the byte slice has an odd length, meaning the text was truncated mid-code-unit and cannot be decoded losslessly.
Source
Thrown at src/file.rs:660
/// ordinary on Windows -- see [`decode_text`], which names them.
pub(crate) fn strip_utf8_bom(s: &str) -> &str {
s.strip_prefix('\u{feff}').unwrap_or(s)
}
/// Decode text that may begin with a byte-order mark.
///
/// `std`'s UTF-8-only readers reject UTF-16 outright, which is how a checksum file sank an install
/// in #5399: PowerShell shipped `hashes.sha256` as UTF-16LE and mise stopped at "stream did not
/// contain valid UTF-8". Windows PowerShell 5.1's `Out-File` writes UTF-16LE by default, so any
/// project generating checksums that way produces the same thing.
///
/// Only a BOM switches the encoding. Detecting UTF-16 without one means guessing from the density
/// of NUL bytes, which can misfire on binary input; `Out-File` always writes a BOM, so the guess
/// buys nothing here. Input with no BOM is decoded as UTF-8, exactly as before.
pub(crate) fn decode_text(bytes: &[u8]) -> Result<String> {
fn from_utf16(bytes: &[u8], to_u16: fn([u8; 2]) -> u16, label: &str) -> Result<String> {
if !bytes.len().is_multiple_of(2) {
bail!(
"truncated {label} text: {} bytes is not a whole number of code units",
bytes.len()
);
}
let units = bytes
.as_chunks::<2>()
.0
.iter()
.map(|c| to_u16(*c))
.collect_vec();
String::from_utf16(&units).wrap_err_with(|| format!("invalid {label} text"))
}
match bytes {
[0xef, 0xbb, 0xbf, rest @ ..] => {
String::from_utf8(rest.to_vec()).wrap_err("invalid UTF-8 text after a UTF-8 BOM")
}
[0xff, 0xfe, rest @ ..] => from_utf16(rest, u16::from_le_bytes, "UTF-16LE"),View on GitHub (pinned to afd2eddd3a)
Solutions
- Re-read or re-download the source so the full byte stream is available
- Ensure the buffer slice boundaries are even before decoding UTF-16 content
- If the source is really UTF-8/ASCII, decode as UTF-8 instead of taking the BOM-selected UTF-16 path
- Handle the trailing odd byte explicitly only if you can prove it is intentional garbage
Example fix
// before let text = decode_text(&buf[..buf.len() - 1])?; // odd-length slice // after let text = decode_text(&buf[..buf.len() & !1])?; // drop to an even boundary, or fix the source
Defensive patterns
Strategy: validation
Validate before calling
if (bytes.length % 2 !== 0) throw new Error('UTF-16 input must have an even byte length, got ' + bytes.length); Type guard
function isWholeUtf16Units(bytes) {
return bytes instanceof Uint8Array && bytes.length % 2 === 0;
} Prevention
- Slice UTF-16 buffers on even offsets only
- Verify downloads completed (size/checksum) before decoding UTF-16 text
- Decode non-BOM text as UTF-8 unless a BOM is present
When it happens
Trigger: Calling decode_text on a byte buffer whose length is odd while a UTF-16LE/UTF-16BE BOM selected the decoder — e.g. a partially read or truncated UTF-16 file or command output stream.
Common situations: Reading Windows PowerShell `Out-File` output cut off by a failed download or interrupted pipe; a UTF-16 file copied incompletely; slicing a UTF-16 buffer at a non-even offset before decode_text.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- invalid relay path encoding
- invalid completion specification path
- history cannot represent a non-UTF-8 filename; refusing to c
- glob pattern is not valid UTF-8: {}
- tracking does not support non-UTF-8 filenames
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/4ec0f5f7be20d6e3.
Report an issue: GitHub.