vercel/turborepo · error · CacheError

Windows returned invalid UTF-16 path: {e}

Error message

Windows returned invalid UTF-16 path: {e}

What it means

On Windows, cache archive creation resolves a handle's true location with GetFinalPathNameByHandleW and then converts the returned UTF-16 buffer into a Rust String (create.rs:664 region). This error means the path Windows itself returned contained ill-formed UTF-16 (e.g. an unpaired surrogate), so String::from_utf16 failed. NTFS permits storing arbitrary UTF-16, so a file with such a name can make the OS hand back an unconvertible path.

Source

Thrown at crates/turborepo-cache/src/cache_archive/create.rs:664

    let mut buffer = vec![0u16; 260];
    loop {
        let len = unsafe {
            GetFinalPathNameByHandleW(
                file.as_raw_handle(),
                buffer.as_mut_ptr(),
                buffer.len() as u32,
                FILE_NAME_NORMALIZED | VOLUME_NAME_DOS,
            )
        };
        if len == 0 {
            return Err(std::io::Error::last_os_error().into());
        }

        let len = len as usize;
        if len < buffer.len() {
            let path = String::from_utf16(&buffer[..len]).map_err(|e| {
                std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    format!("Windows returned invalid UTF-16 path: {e}"),
                )
            })?;
            return Ok(std::path::PathBuf::from(strip_windows_verbatim_prefix(
                &path,
            )));
        }

        buffer.resize(len + 1, 0);
    }
}

#[cfg(windows)]
fn strip_windows_verbatim_prefix(path: &str) -> String {
    if let Some(rest) = path.strip_prefix(r"\\?\UNC\") {
        format!(r"\\{rest}")
    } else if let Some(rest) = path.strip_prefix(r"\\?\") {

View on GitHub (pinned to f9245100cf)

Solutions

  1. Locate the offending file (Explorer/PowerShell often render it as '?' or garbled) in the directory turbo was archiving and rename or delete it
  2. Exclude the affected path from task inputs/outputs in turbo.json so it never enters the archive
  3. Run `chkdsk` if you suspect filesystem corruption
  4. Report it with the reproducing path so the guard can be improved

Example fix

// PowerShell: find files with surrogate-ish names under the output dir
Get-ChildItem -Recurse -LiteralPath .\dist | Where-Object { $_.Name -match '[\uD800-\uDFFF]' } | Rename-Item -NewName { $_.Name -replace '[\uD800\uDFFF]', '_' }
Defensive patterns

Strategy: try-catch

Validate before calling

// Windows: pre-flight names you control before archiving
fn utf16_ok(s: &str) -> bool { s.encode_utf16().count() > 0 } // Rust strs are valid; only OS-returned buffers can fail

Try / catch

// cache_archive::create surfaces std::io::Error with kind()==InvalidData
if let Err(e) = archive_create(...) {
    if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("invalid UTF-16") {
        // quarantine the offending path and continue with the rest
    }
}

Prevention

When it happens

Trigger: Hashing/archiving a file whose final (normalized, verbatim-stripped) path contains unpaired surrogate code units, during the realpath resolution loop in create.rs.

Common situations: Files created by buggy scripts or third-party installers that wrote invalid UTF-16 names Disk or MFT corruption after a crash Extremely rare on normal machines

Understand the failure class

Related errors


AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17). Data as JSON: /api/errors/20a553c61f0bd85e. Report an issue: GitHub.