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
- Locate the offending file (Explorer/PowerShell often render it as '?' or garbled) in the directory turbo was archiving and rename or delete it
- Exclude the affected path from task inputs/outputs in turbo.json so it never enters the archive
- Run `chkdsk` if you suspect filesystem corruption
- 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
- Keep generated filenames to ASCII/safe Unicode
- Validate third-party tooling that writes files into build outputs
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- anchored system path is relative: {}
- path component contains NUL byte: {component:?}
- socket path has no parent: {sock_path}
- invalid SID string from Windows: {e}
- which git produced an invalid absolute path {}
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/20a553c61f0bd85e.
Report an issue: GitHub.