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

  1. Re-run the task once to rule out transient corruption; if it reproduces, the tree really has an offending entry
  2. 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
  3. If it only reproduces on a cache hit replay, clear the local and remote turbo cache and rebuild
  4. 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

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


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