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
- 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
- Remove or rename the offending non-directory node, then re-run the operation so the directory is created fresh
- 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
- Never write regular files at the same path the library uses for its private directory
- Check metadata(path).is_dir() before initializing
- Keep app state files under a different name than the private directory
- Detect stale files after version upgrades and migrate them
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
- layout migration source contains a special file
- legacy distro
- legacy source entry is group/world writable
- legacy source entry is not owned by the current user
- outside fixture
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"));
}
pathView on GitHub (pinned to affd8760f4)