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

daemon socket parent is not a directory: {socket_dir}

Error message

daemon socket parent is not a directory: {socket_dir}

What it means

On Unix, secure_unix_dir (endpoint.rs:498) creates the socket parent dir with mode 0700, then stats it with symlink_metadata and requires file_type().is_dir(). This PermissionDenied error means something other than a directory occupies the path — a regular file, FIFO, or notably a symlink (lstat does not follow links, so a symlink-to-directory also fails the check). The daemon refuses to operate through a swappable path.

Source

Thrown at crates/turborepo-daemon/src/endpoint.rs:498

    })?;
    if let Some(daemon_dir) = socket_dir.parent() {
        secure_unix_dir(daemon_dir)?;
    }
    secure_unix_dir(socket_dir)
}

#[cfg(unix)]
fn secure_unix_dir(socket_dir: &AbsoluteSystemPath) -> Result<(), std::io::Error> {
    use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};

    std::fs::DirBuilder::new()
        .recursive(true)
        .mode(PRIVATE_DIR_MODE)
        .create(socket_dir.as_std_path())?;

    let metadata = std::fs::symlink_metadata(socket_dir.as_std_path())?;
    if !metadata.file_type().is_dir() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::PermissionDenied,
            format!("daemon socket parent is not a directory: {socket_dir}"),
        ));
    }
    if metadata.uid() != current_uid() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::PermissionDenied,
            format!("daemon socket parent is owned by another user: {socket_dir}"),
        ));
    }

    let mode = metadata.permissions().mode() & 0o777;
    if mode != PRIVATE_DIR_MODE {
        std::fs::set_permissions(
            socket_dir.as_std_path(),
            std::fs::Permissions::from_mode(PRIVATE_DIR_MODE),
        )?;
    }

View on GitHub (pinned to f9245100cf)

Solutions

  1. Inspect the path in the message with `ls -la` / `file <path>`
  2. Remove the non-directory entry (rm the file, or unlink the symlink — do not delete through it)
  3. Restart the daemon (turbo daemon restart) so it recreates a real directory

Example fix

# bash: inspect and clear the offending entry
file ~/.cache/turborepo
rm -i ~/.cache/turborepo   # or: unlink ~/.cache/turborepo if a symlink
turbo daemon restart
Defensive patterns

Strategy: validation

Validate before calling

// before daemon start, ensure the socket parent is a real directory
let m = std::fs::symlink_metadata(socket_dir)?; // lstat: does not follow symlinks
if !m.is_dir() {
    anyhow::bail!("{socket_dir} exists but is not a directory");
}

Type guard

fn is_real_dir(p: &Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_dir()).unwrap_or(false)
}

Try / catch

match daemon_start() {
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied
        && e.to_string().contains("not a directory") => { std::fs::remove_file(socket_dir).ok(); daemon_start()? }
    r => r,
}

Prevention

When it happens

Trigger: Daemon start when the socket dir path (e.g. ~/.cache/turborepo or XDG_RUNTIME_DIR/turborepo) is occupied by a file or symlink left by another tool, a dotfile manager, or a previous mishap.

Common situations: Users symlinking cache/runtime dirs to tmpfs or a shared folder Another application claiming the same directory name Leftover artifacts from a crashed daemon or manual experiments

Related errors


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