vercel/turborepo · error · std::io::Error

{path}: not a regular file

Error message

{path}: not a regular file

What it means

When hashing workspace files for the cache key, the CRLF-aware blob hasher first validates the file type (crlf.rs:333): anything that is neither a regular file nor a directory — sockets, FIFOs, char/block devices — is rejected with InvalidInput '{path}: not a regular file'. Directories deliberately pass through to fail later with a descriptive IsADirectory error; this guard targets exotic node types that would otherwise hash garbage or hang on a FIFO.

Source

Thrown at crates/turborepo-scm/src/crlf.rs:333

}

/// Hash a byte slice as a git blob. Used to verify symlink entries, whose
/// blob content is the link target path (no filters ever apply).
pub(crate) fn hash_bytes_as_blob(bytes: &[u8]) -> Result<OidHash, std::io::Error> {
    let mut hasher = BlobHasher::new();
    hasher.write_blob_header(bytes.len() as u64);
    hasher.update(bytes);
    hasher.finalize()
}

fn validate_file_type(
    path: &AbsoluteSystemPath,
    metadata: &std::fs::Metadata,
) -> Result<(), std::io::Error> {
    // Reject exotic file types (sockets, FIFOs, device nodes). Directories
    // pass through to fail naturally with a descriptive IsADirectory error.
    if !metadata.is_file() && !metadata.is_dir() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            format!("{path}: not a regular file"),
        ));
    }
    Ok(())
}

/// Hash a file as a git blob, applying CRLF→LF normalization when the
/// `text` attribute requires it.
///
/// For `TextAttr::Auto`, binary detection (NUL in first 8KB) is performed
/// during the fused scan+hash pass — no separate file open.
///
/// Single-pass for the common case (no normalization needed):
/// 1. Get file length from metadata
/// 2. Fused scan + speculative raw hash: write blob header, then scan for CRLFs
///    while simultaneously feeding raw bytes into the hasher
/// 3. If no normalization needed, return the raw hash (one read total)

View on GitHub (pinned to 7fe373bc27)

Solutions

  1. Find the offending entry: the message names the exact path; `file <path>` confirms the type
  2. Delete it or move the tool that creates it outside the workspace
  3. Tighten turbo.json `inputs` globs to specific extensions/dirs so sockets and FIFOs are excluded

Example fix

// before (turbo.json)
"inputs": ["**/*"]
// after — exclude socket/fifo artifacts
"inputs": ["src/**/*", "!**/*.sock", "!run/**"]
Defensive patterns

Strategy: type-guard

Validate before calling

// exclude non-regular files before hashing inputs
for entry in walkdir::WalkDir::new(dir) {
    let e = entry?;
    let m = e.metadata()?;
    if !(m.is_file() || m.is_dir()) { continue; } // skip sockets, fifos, devices
    hash_file(e.path())?;
}

Type guard

fn is_hashable(m: &std::fs::Metadata) -> bool {
    // uses symlink-free metadata: mirrors validate_file_type in crlf.rs
    m.is_file() || m.is_dir()
}

Try / catch

match hash_path(path) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput
        && e.to_string().contains("not a regular file") => { skipped.push(path); continue; }
    r => r?,
}

Prevention

When it happens

Trigger: A glob/task input in turbo.json matching a Unix socket or named pipe inside the workspace — e.g. a dev server dropped a *.sock file, or a tools directory contains an mkfifo artifact that a broad `inputs` pattern picks up.

Common situations: dev/.sock or *.socket files created by local daemons inside the repo FIFOs from ad-hoc scripts left in the tree Repo unpacked over a path containing device nodes (rare)

Related errors


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