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

  1. Pass an absolute path beginning with `/` (e.g. `Path::new("/home/user/.astrid")`) instead of a relative or empty path
  2. 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
  3. Check that the path contains at least one `Normal` component and starts with `Component::RootDir` before calling
  4. 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

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/fe66bcc95362b56d. Report an issue: GitHub.