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
- Inspect the path in the message with `ls -la` / `file <path>`
- Remove the non-directory entry (rm the file, or unlink the symlink — do not delete through it)
- 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
- Do not symlink the daemon/cache runtime dir; mount tmpfs instead
- Check `file <path>` when reusing nonstandard runtime dirs
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
- socket path has no parent: {sock_path}
- daemon socket parent is owned by another user: {socket_dir}
- daemon peer uid {peer_uid} does not match current uid {curre
- {path}: not a regular file
- Unable to write .gitignore
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/8397fe7055308657.
Report an issue: GitHub.