{"record":{"id":"53bd1c3f18b54059","repo":"astrid-runtime/astrid","slug":"legacy-principal-local-path-is-not-a-directory","errorCode":null,"errorMessage":"legacy principal .local path is not a directory: {}","messagePattern":"legacy principal \\.local path is not a directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":402,"sourceCode":"                    \"legacy principal-home entry is not a regular directory: {}\",\n                    principal_root.display()\n                ),\n            ));\n        }\n        if device_id(&principal_metadata) != root_device {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy principal-home entry crosses a filesystem boundary: {}\",\n                    principal_root.display()\n                ),\n            ));\n        }\n        astrid_core::platform_fs::verify_no_redirects(&principal_root)?;\n        let local_root = principal_root.join(\".local\");\n        match fs::symlink_metadata(&local_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 .local path is not a directory: {}\",\n                        local_root.display()\n                    ),\n                ));\n            },\n            Ok(_) => astrid_core::platform_fs::verify_no_redirects(&local_root)?,\n            Err(error) if error.kind() == io::ErrorKind::NotFound => continue,\n            Err(error) => return Err(error),\n        }\n        let audit_source = local_root.join(\"audit\");\n        let audit_metadata = match fs::symlink_metadata(&audit_source) {\n            Ok(metadata) => metadata,\n            Err(error) if error.kind() == io::ErrorKind::NotFound => continue,\n            Err(error) => return Err(error),\n        };\n        if audit_source == default_source {","sourceCodeStart":384,"sourceCodeEnd":420,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L384-L420","documentation":"For each principal directory, preflight_legacy_audit_sources checks the .local subpath: if it exists but is a symlink or a non-directory, the migration aborts with InvalidData. Following a redirected .local could let the importer operate on audit data outside the principal's own tree, so redirects are rejected outright (absence is fine and is skipped).","triggerScenarios":"migrate_legacy_audit with a principal dir containing <principal>/.local as a symlink, regular file, or special file (host_fs.rs:401). A NotFound .local is allowed and simply skipped via continue.","commonSituations":"Symlinking .local to a shared or dotfiles-managed location; a file named .local created by accident; XDG-style redirection tooling replacing the directory with a link.","solutions":["Replace the .local symlink/file with a real directory (copy the linked contents into place).","Remove the stray file occupying the .local path and recreate it as a directory with private permissions.","Reconfigure the redirection tool (dotfile manager, XDG env setup) so .local stays a physical directory.","If .local is not needed, remove the entry entirely — absence is accepted by the preflight."],"exampleFix":"// before\n.local -> /dotfiles/local\n// after\n$ rm .local && mkdir -m 700 .local && cp -a /dotfiles/local/. .local/","handlingStrategy":"validation","validationCode":"fn local_path_ok(principal_root: &std::path::Path) -> std::io::Result<bool> {\n    let local = principal_root.join(\".local\");\n    match std::fs::symlink_metadata(&local) {\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_or_missing(p: &std::path::Path) -> bool {\n    match std::fs::symlink_metadata(p) {\n        Ok(m) => !m.file_type().is_symlink() && m.is_dir(),\n        Err(e) => e.kind() == std::io::ErrorKind::NotFound,\n    }\n}","tryCatchPattern":"match result {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains(\".local path is not a directory\") => {\n        // replace the symlink/file at .local with a real directory, then retry\n    }\n    other => other?,\n}","preventionTips":["Keep .local as a real directory under each principal root.","Audit with find <principal-root> -name .local -type l before migrating.","Configure XDG/dotfile tooling not to replace .local with a symlink."],"tags":["filesystem","symlink","migration","xdg"],"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"}