{"record":{"id":"161aca13e58275a3","repo":"astrid-runtime/astrid","slug":"private-path-is-not-a-directory","errorCode":null,"errorMessage":"private path is not a directory","messagePattern":"private path is not a directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":199,"sourceCode":"///\n/// Returns an error when the directory is missing, redirected, not owned by the\n/// current user, or does not satisfy the platform private-access policy.\npub fn validate_private_directory(path: &Path) -> io::Result<()> {\n    #[cfg(unix)]\n    {\n        validate_private_directory_unix(path)\n    }\n\n    #[cfg(windows)]\n    {\n        windows::ensure_private_directory(path)\n    }\n\n    #[cfg(not(any(unix, windows)))]\n    {\n        let metadata = std::fs::symlink_metadata(path)?;\n        if !metadata.is_dir() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"private path is not a directory\",\n            ));\n        }\n        Ok(())\n    }\n}\n\n/// Rename one filesystem entry with the strongest supported namespace\n/// durability for the host platform.\n///\n/// Windows uses `MoveFileExW(MOVEFILE_WRITE_THROUGH)`, which does not return\n/// until the move has been flushed. Unix callers must still synchronize the\n/// affected parent directories after this atomic rename.\n///\n/// Windows rejects an existing destination because the write-through move does\n/// not request replacement. Other platforms retain `std::fs::rename`\n/// replacement semantics. Security-sensitive callers remain responsible for","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L181-L217","documentation":"On targets that are neither Unix nor Windows, validate_private_directory falls back to a weak check: it only verifies the path is a directory via symlink_metadata. This error means the given path exists but is not a directory (e.g. it is a file or symlink to a file), so the private-directory contract cannot be validated.","triggerScenarios":"Calling validate_private_directory(path) on a non-unix/non-windows target where `path` resolves to a regular file, device, or other non-directory entry.","commonSituations":"Config pointing at a file instead of a directory; a stale file left where a data directory should be; porting the library to a platform (e.g. wasm) with the wrong path configured.","solutions":["Check the path and replace the file with the intended directory, or point configuration at the correct directory path.","Create the directory first (ensure_private_directory / create_dir_all) before validating.","If you only need existence+type checks on exotic targets, guard the call with std::path::Path::is_dir() first."],"exampleFix":"// before\nvalidate_private_directory(&cfg.private_dir)?;\n// after\nif !cfg.private_dir.is_dir() {\n    return Err(format!(\"{} is not a directory; check your config\", cfg.private_dir.display()).into());\n}\nvalidate_private_directory(&cfg.private_dir)?;","handlingStrategy":"validation","validationCode":"if !path.is_dir() {\n    return Err(format!(\"private path {} is not a directory\", path.display()));\n}","typeGuard":"fn is_real_directory(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":"match validate_private_directory(&path) {\n    Err(e) if e.to_string().contains(\"not a directory\") => {\n        std::fs::create_dir_all(&path)?;\n        validate_private_directory(&path)?;\n    }\n    other => other?,\n}","preventionTips":["Verify configured paths point at directories before validating","Create directories with ensure_private_directory instead of pointing config at ad-hoc paths","Check for stray files left where directories are expected after failed installs"],"tags":["io","filesystem","path-validation"],"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"}