vercel/turborepo · error · CacheError
path component contains NUL byte: {component:?}
Error message
path component contains NUL byte: {component:?} What it means
While building a cache archive tar, turborepo walks the tree with openat/fstatat/readlinkat relative to a parent fd, and each single path component must be converted to a C string first (crates/turborepo-cache/src/cache_archive/create.rs:502, used at lines 423/438/453/475). This error means one component contained an embedded NUL byte, which CString::new rejects. On Unix a real filesystem name can never contain NUL, so this is a defensive guard against in-memory or corrupted names, not a normal runtime condition.
Source
Thrown at crates/turborepo-cache/src/cache_archive/create.rs:504
if len == -1 {
return Err(std::io::Error::last_os_error().into());
}
let len = len as usize;
if len < buffer.len() {
buffer.truncate(len);
let target = PathBuf::from(std::ffi::OsString::from_vec(buffer));
return Ok(camino::Utf8PathBuf::try_from(target).map_err(turbopath::PathError::from)?);
}
buffer.resize(buffer.len() * 2, 0);
}
}
#[cfg(unix)]
fn path_component_cstring(component: &str) -> Result<std::ffi::CString, CacheError> {
std::ffi::CString::new(component).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("path component contains NUL byte: {component:?}"),
)
.into()
})
}
#[cfg(unix)]
fn create_header_from_stat(file_info: &libc::stat) -> Result<Header, CacheError> {
let mut header = Header::new_gnu();
header.set_mode(unix_mode_to_u32(file_info.st_mode));
let file_type = file_info.st_mode & libc::S_IFMT;
if file_type == libc::S_IFLNK {
header.set_entry_type(EntryType::Symlink);
header.set_size(0);
} else if file_type == libc::S_IFDIR {
header.set_entry_type(EntryType::Directory);View on GitHub (pinned to f9245100cf)
Solutions
- Re-run the task once to rule out transient corruption; if it reproduces, the tree really has an offending entry
- Inspect the traced directory with `ls -b` or `find <dir> -print0 | tr -c '\000\n' '?'` to spot non-printable/NUL-containing names and rename or delete them
- If it only reproduces on a cache hit replay, clear the local and remote turbo cache and rebuild
- Report a bug with the exact path from the message — on Unix this should be unreachable
Example fix
# shell: find the offending name find . -name '*' -print0 | tr -c '\000\n' '?\n' | grep -B1 '?' # rename or remove the file it flags, then re-run: npx turbo run build --force
Defensive patterns
Strategy: validation
Validate before calling
// before walking a tree into the cache archive, reject NUL components
fn components_ok(path: &camino::Utf8Path) -> bool {
path.components().all(|c| !c.as_str().contains('\0'))
}
assert!(components_ok(&path)); Type guard
fn is_nul_free(p: &str) -> bool { !p.contains('\0') } Try / catch
match result {
Err(CacheError::Io(e)) if e.kind() == std::io::ErrorKind::InvalidInput
&& e.to_string().contains("NUL byte") => { /* skip entry, log path */ }
other => other?,
} Prevention
- Never build path components from raw untrusted bytes without validating them
- Treat a NUL-component hit as corruption: stop and investigate instead of retrying
When it happens
Trigger: Cache archive creation (save-to-cache) reaching open_dir_at / open_file_at / fstatat_no_follow / read_link_at with a component string that contains a '\0' — i.e. a component that did not come straight from a healthy readdir, or a readdir buffer that is corrupt.
Common situations: Essentially never seen on healthy systems. When it appears it points to data corruption (bad readdir, memory bug) or a path synthesized from untrusted bytes that smuggled in a NUL.
Related errors
- Windows returned invalid UTF-16 path: {e}
- {path}: not a regular file
- Directory path contains potentially unsafe characters: ${dir
- plop_no_config
- package_manager-could_not_be_found
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/b4ccdc3e3dfa42bf.
Report an issue: GitHub.