{"record":{"id":"08f53c5f2db288cc","repo":"astrid-runtime/astrid","slug":"legacy-principal-home-root-is-not-a-directory","errorCode":null,"errorMessage":"legacy principal-home root is not a directory: {}","messagePattern":"legacy principal-home root is not a directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":362,"sourceCode":"                child.display()\n            ),\n        ));\n    }\n    fs::remove_file(child).map_err(io::Error::other)\n}\n\n/// Validate the released audit-source boundary on every native host.  Unix\n/// adds device and mount checks; all hosts retain no-follow, regular-entry,\n/// and default-principal-only checks before the audit importer opens a source.\n#[cfg(not(unix))]\npub(super) fn preflight_legacy_audit_sources(\n    home: &AstridHome,\n    default_source: &Path,\n) -> io::Result<bool> {\n    let root = home.home_dir();\n    let metadata = match fs::symlink_metadata(&root) {\n        Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy principal-home root is not a directory: {}\",\n                    root.display()\n                ),\n            ));\n        },\n        Ok(metadata) => metadata,\n        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),\n        Err(error) => return Err(error),\n    };\n    let root_device = device_id(&metadata);\n    let mut default_source_present = false;\n    astrid_core::platform_fs::verify_no_redirects(&root)?;\n    for entry in fs::read_dir(&root).map_err(io::Error::other)? {\n        let principal_root = entry.map_err(io::Error::other)?.path();\n        let principal_metadata = fs::symlink_metadata(&principal_root).map_err(io::Error::other)?;\n        if principal_metadata.file_type().is_symlink() || !principal_metadata.is_dir() {","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L344-L380","documentation":"preflight_legacy_audit_sources (non-Unix host path) validates that the principal home root is a plain directory before scanning it for legacy audit sources. If symlink_metadata reports the root is a symlink or not a directory, the migration aborts with InvalidData, because following a redirected home root could make the importer read or retire data outside the intended boundary.","triggerScenarios":"Calling migrate_legacy_audit when the principal home directory path (home.home_dir()) is itself a symlink, a regular file, or another non-directory entry (host_fs.rs:361). Note NotFound is tolerated and returns Ok(false), so only existing-but-wrong types trigger this.","commonSituations":"Users who symlink their home directory to another location (common with dotfile managers); a file accidentally created at the home path; misconfigured ASTRID home env var pointing at a file.","solutions":["Replace the symlink at the home root with a real directory (mv the target contents back or use a bind mount).","Remove/rename the stray file occupying the home path and recreate it as a directory.","Point the home configuration (env var/config) at the actual directory location instead of a symlink.","Re-run migrate_legacy_audit once home.home_dir() resolves to a physical directory."],"exampleFix":"// before: dotfile-manager symlink\n/home/u/.astrid -> /dotfiles/astrid\n// after\n$ rm /home/u/.astrid && mkdir /home/u/.astrid && cp -a /dotfiles/astrid/. /home/u/.astrid/","handlingStrategy":"validation","validationCode":"fn home_root_ok(home_dir: &std::path::Path) -> std::io::Result<bool> {\n    match std::fs::symlink_metadata(home_dir) {\n        Ok(m) => Ok(!m.file_type().is_symlink() && m.is_dir()),\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true), // tolerated\n        Err(e) => Err(e),\n    }\n}","typeGuard":"fn is_plain_dir(m: &std::fs::Metadata) -> bool {\n    !m.file_type().is_symlink() && m.is_dir()\n}","tryCatchPattern":"match result {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains(\"root is not a directory\") => {\n        // fix the home path (de-symlink / recreate as directory), then retry\n    }\n    other => other?,\n}","preventionTips":["Do not symlink the astrid home root; use real directories.","If redirection is needed, mount rather than symlink so the path is a physical directory.","Validate the configured home path with symlink_metadata before running migrations."],"tags":["filesystem","symlink","migration","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-14T05:17:10.506Z"}