{"record":{"id":"4ec0f5f7be20d6e3","repo":"jdx/mise","slug":"truncated-label-text-bytes-is-not-a-whole-nu","errorCode":null,"errorMessage":"truncated {label} text: {} bytes is not a whole number of code units","messagePattern":"truncated (.+?) text: (.+?) bytes is not a whole number of code units","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/file.rs","lineNumber":660,"sourceCode":"/// ordinary on Windows -- see [`decode_text`], which names them.\npub(crate) fn strip_utf8_bom(s: &str) -> &str {\n    s.strip_prefix('\\u{feff}').unwrap_or(s)\n}\n\n/// Decode text that may begin with a byte-order mark.\n///\n/// `std`'s UTF-8-only readers reject UTF-16 outright, which is how a checksum file sank an install\n/// in #5399: PowerShell shipped `hashes.sha256` as UTF-16LE and mise stopped at \"stream did not\n/// contain valid UTF-8\". Windows PowerShell 5.1's `Out-File` writes UTF-16LE by default, so any\n/// project generating checksums that way produces the same thing.\n///\n/// Only a BOM switches the encoding. Detecting UTF-16 without one means guessing from the density\n/// of NUL bytes, which can misfire on binary input; `Out-File` always writes a BOM, so the guess\n/// buys nothing here. Input with no BOM is decoded as UTF-8, exactly as before.\npub(crate) fn decode_text(bytes: &[u8]) -> Result<String> {\n    fn from_utf16(bytes: &[u8], to_u16: fn([u8; 2]) -> u16, label: &str) -> Result<String> {\n        if !bytes.len().is_multiple_of(2) {\n            bail!(\n                \"truncated {label} text: {} bytes is not a whole number of code units\",\n                bytes.len()\n            );\n        }\n        let units = bytes\n            .as_chunks::<2>()\n            .0\n            .iter()\n            .map(|c| to_u16(*c))\n            .collect_vec();\n        String::from_utf16(&units).wrap_err_with(|| format!(\"invalid {label} text\"))\n    }\n\n    match bytes {\n        [0xef, 0xbb, 0xbf, rest @ ..] => {\n            String::from_utf8(rest.to_vec()).wrap_err(\"invalid UTF-8 text after a UTF-8 BOM\")\n        }\n        [0xff, 0xfe, rest @ ..] => from_utf16(rest, u16::from_le_bytes, \"UTF-16LE\"),","sourceCodeStart":642,"sourceCodeEnd":678,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/file.rs#L642-L678","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet text = decode_text(&buf[..buf.len() - 1])?; // odd-length slice\n// after\nlet text = decode_text(&buf[..buf.len() & !1])?; // drop to an even boundary, or fix the source","handlingStrategy":"validation","validationCode":"if (bytes.length % 2 !== 0) throw new Error('UTF-16 input must have an even byte length, got ' + bytes.length);","typeGuard":"function isWholeUtf16Units(bytes) {\n  return bytes instanceof Uint8Array && bytes.length % 2 === 0;\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["encoding","utf-16","decoding"],"backgroundTag":"invalid-argument-format","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}