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

  1. Re-read or re-download the source so the full byte stream is available
  2. Ensure the buffer slice boundaries are even before decoding UTF-16 content
  3. If the source is really UTF-8/ASCII, decode as UTF-8 instead of taking the BOM-selected UTF-16 path
  4. 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

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/4ec0f5f7be20d6e3. Report an issue: GitHub.