astrid-runtime/astrid · error
private directory has no Unix root: {}
Error message
private directory has no Unix root: {} What it means
This error is thrown by `unix_directory_walk` when a private-directory path, after normalization and resolution against the current working directory, does not start with a Unix root component (`/`). The function opens `/` as the starting directory handle for a no-follow openat walk; without a root it cannot anchor the walk, so it refuses with `InvalidInput`. It indicates the caller passed a path the library cannot represent as an absolute Unix directory path.
Source
Thrown at crates/astrid-core/src/platform_fs.rs:726
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("private directory contains traversal: {}", path.display()),
));
}
let absolute = normalize_unix_system_alias(if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
});
let mut directory = if absolute
.components()
.next()
.is_some_and(|component| matches!(component, Component::RootDir))
{
std::fs::File::open("/")
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("private directory has no Unix root: {}", path.display()),
));
}?;
let mut missing = Vec::new();
for component in absolute
.components()
.filter_map(|component| match component {
Component::Normal(name) => Some(name.to_os_string()),
_ => None,
})
{
if missing.is_empty() {
let flags = OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC;
match openat(&directory, component.as_os_str(), flags, Mode::empty()) {
Ok(next) => directory = std::fs::File::from(next),
Err(Errno::ENOENT) => missing.push(component),
Err(error) => return Err(nix_io_error(error)),View on GitHub (pinned to affd8760f4)
Solutions
- Pass an absolute path beginning with `/` (e.g. `Path::new("/home/user/.astrid")`) instead of a relative or empty path
- If starting from a relative path, join it onto a known rooted base such as `std::env::current_dir()` or `dirs::home_dir()` before calling
- Check that the path contains at least one `Normal` component and starts with `Component::RootDir` before calling
- Verify the process's current working directory is valid and rooted if relying on relative-path resolution
Example fix
// before
ensure_private_directory(Path::new(".astrid/keys"))?
// after
let base = dirs::home_dir().context("no home dir")?;
ensure_private_directory(&base.join(".astrid/keys"))? Defensive patterns
Strategy: validation
Validate before calling
fn is_rooted_unix_dir(path: &Path) -> bool {
path.is_absolute() && path.components().any(|c| matches!(c, std::path::Component::Normal(_)))
}
// call only if is_rooted_unix_dir(&private_dir) Type guard
fn has_unix_root(path: &Path) -> bool {
matches!(path.components().next(), Some(std::path::Component::RootDir))
} Try / catch
match ensure_private_directory(&dir) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => eprintln!("not an absolute Unix path: {e}"),
Err(e) => return Err(e.into()),
Ok(()) => {},
} Prevention
- Always pass absolute paths starting with '/' to private-directory APIs
- Anchor relative paths to home_dir() or current_dir() at construction time
- Never pass empty or '.'-only paths
- Validate with Path::is_absolute() before calling
When it happens
Trigger: Calling `ensure_private_directory_unix` or `open_directory_no_follow_unix` with a path that normalizes to nothing anchored at `/` — e.g. an empty path, a bare relative name like `foo` when `std::env::current_dir()` itself yields a non-rooted path, or a path composed only of `.`/`CurDir` components so the resulting absolute path has no `RootDir` component.
Common situations: Passing an empty string or `"."` as a private directory path; a corrupted `PWD`/current-dir environment so relative paths fail to anchor; tests or tools that construct `PathBuf` paths from fragments without joining to a rooted base.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- capsule source is neither a directory nor a regular file: {}
- Source path does not exist: {source}
- source path does not exist: {source}
- mountpoint is not a directory: {}
- leftover capsule authority receipt has no file name: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/fe66bcc95362b56d.
Report an issue: GitHub.