astrid-runtime/astrid · error

private directory is not a directory

Error message

private directory is not a directory: {}

What it means

Thrown at the end of `unix_directory_walk`: after walking the path component-by-component with `openat(O_DIRECTORY|O_NOFOLLOW)`, the final handle's metadata is not a directory, so the operation is aborted with `InvalidData`. The library requires the private path to be a real directory, not a file or other node.

Solutions

  1. Check the path with `std::fs::metadata(path)` and confirm `is_dir()` before calling; if it is a file, move or remove it and let the library create the directory
  2. Remove or rename the offending non-directory node, then re-run the operation so the directory is created fresh
  3. If the path holds data you need, relocate it to a different name before initializing the private directory

Example fix

// before
ensure_private_directory(&config_dir)?; // config_dir is actually a file
// after
let meta = std::fs::metadata(&config_dir)?;
if !meta.is_dir() {
    std::fs::rename(&config_dir, config_dir.with_extension("file.bak"))?;
    std::fs::create_dir_all(&config_dir)?;
}
ensure_private_directory(&config_dir)?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_dir_node(path: &Path) -> std::io::Result<()> {
    match std::fs::symlink_metadata(path) {
        Ok(m) if m.is_dir() => Ok(()),
        Ok(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "path exists but is not a directory")),
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => std::fs::create_dir_all(path),
        Err(e) => Err(e),
    }
}

Type guard

fn is_existing_directory(path: &Path) -> bool {
    std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
}

Try / catch

if let Err(e) = ensure_private_directory(&dir) {
    if e.kind() == std::io::ErrorKind::InvalidData {
        eprintln!("{} exists but is not a directory; move it aside", dir.display());
    }
    return Err(e.into());
}

Prevention

When it happens

Trigger: Calling `ensure_private_directory_unix` or `open_directory_no_follow_unix` with a path that exists but is a regular file, symlink target file, FIFO, or other non-directory — e.g. `~/.astrid` exists as a plain file created earlier by mistake, and the code then tries to ensure/use it as a directory.

Common situations: A config or state file accidentally occupies the directory name the library expects (e.g. `.astrid` is a file); a prior tool version wrote a file where the new version expects a directory; restoring from backups with the wrong node type.

Related errors


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

Appendix: source

Thrown at crates/astrid-core/src/platform_fs.rs:754

            _ => 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)),
            }
        } else {
            missing.push(component);
        }
    }

    if directory.metadata()?.is_dir() {
        Ok((directory, missing))
    } else {
        Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("private directory is not a directory: {}", path.display()),
        ))
    }
}

#[cfg(target_os = "macos")]
fn normalize_unix_system_alias(path: PathBuf) -> PathBuf {
    use std::os::unix::ffi::OsStrExt as _;

    let bytes = path.as_os_str().as_bytes();
    if bytes == b"/tmp" || bytes.starts_with(b"/tmp/") {
        return PathBuf::from("/private/tmp").join(path.strip_prefix("/tmp").expect("prefix"));
    }
    if bytes == b"/var" || bytes.starts_with(b"/var/") {
        return PathBuf::from("/private/var").join(path.strip_prefix("/var").expect("prefix"));
    }
    path

View on GitHub (pinned to affd8760f4)