{"record":{"id":"62e210ffee4c833a","repo":"astrid-runtime/astrid","slug":"private-directory-is-not-a-directory","errorCode":null,"errorMessage":"private directory is not a directory: {}","messagePattern":"private directory is not a directory: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":754,"sourceCode":"            _ => None,\n        })\n    {\n        if missing.is_empty() {\n            let flags = OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC;\n            match openat(&directory, component.as_os_str(), flags, Mode::empty()) {\n                Ok(next) => directory = std::fs::File::from(next),\n                Err(Errno::ENOENT) => missing.push(component),\n                Err(error) => return Err(nix_io_error(error)),\n            }\n        } else {\n            missing.push(component);\n        }\n    }\n\n    if directory.metadata()?.is_dir() {\n        Ok((directory, missing))\n    } else {\n        Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"private directory is not a directory: {}\", path.display()),\n        ))\n    }\n}\n\n#[cfg(target_os = \"macos\")]\nfn normalize_unix_system_alias(path: PathBuf) -> PathBuf {\n    use std::os::unix::ffi::OsStrExt as _;\n\n    let bytes = path.as_os_str().as_bytes();\n    if bytes == b\"/tmp\" || bytes.starts_with(b\"/tmp/\") {\n        return PathBuf::from(\"/private/tmp\").join(path.strip_prefix(\"/tmp\").expect(\"prefix\"));\n    }\n    if bytes == b\"/var\" || bytes.starts_with(b\"/var/\") {\n        return PathBuf::from(\"/private/var\").join(path.strip_prefix(\"/var\").expect(\"prefix\"));\n    }\n    path","sourceCodeStart":736,"sourceCodeEnd":772,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L736-L772","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nensure_private_directory(&config_dir)?; // config_dir is actually a file\n// after\nlet meta = std::fs::metadata(&config_dir)?;\nif !meta.is_dir() {\n    std::fs::rename(&config_dir, config_dir.with_extension(\"file.bak\"))?;\n    std::fs::create_dir_all(&config_dir)?;\n}\nensure_private_directory(&config_dir)?;","handlingStrategy":"validation","validationCode":"fn ensure_dir_node(path: &Path) -> std::io::Result<()> {\n    match std::fs::symlink_metadata(path) {\n        Ok(m) if m.is_dir() => Ok(()),\n        Ok(_) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, \"path exists but is not a directory\")),\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => std::fs::create_dir_all(path),\n        Err(e) => Err(e),\n    }\n}","typeGuard":"fn is_existing_directory(path: &Path) -> bool {\n    std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":"if let Err(e) = ensure_private_directory(&dir) {\n    if e.kind() == std::io::ErrorKind::InvalidData {\n        eprintln!(\"{} exists but is not a directory; move it aside\", dir.display());\n    }\n    return Err(e.into());\n}","preventionTips":["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"],"tags":["filesystem","invalid-data","unix","not-a-directory"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}