{"record":{"id":"db0fe7999baba08b","repo":"astrid-runtime/astrid","slug":"legacy-audit-tree-is-redirected-or-not-a-directory","errorCode":null,"errorMessage":"legacy audit tree is redirected or not a directory: {}","messagePattern":"legacy audit tree is redirected or not a directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":514,"sourceCode":"        },\n        Err(error) if error.kind() == io::ErrorKind::NotFound => {\n            if fs::symlink_metadata(&retired).is_ok() {\n                let root_device = device_id(&fs::symlink_metadata(&retired)?);\n                validate_audit_tree(&retired, root_device)?;\n                delete_audit_tree(&retired, root_device)?;\n                sync_directory(&home.migrations_dir())?;\n            }\n        },\n        Err(error) => return Err(error),\n    }\n    Ok(())\n}\n\n#[cfg(not(unix))]\nfn validate_audit_tree(path: &Path, root_device: u64) -> io::Result<()> {\n    let metadata = fs::symlink_metadata(path)?;\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\n                \"legacy audit tree is redirected or not a directory: {}\",\n                path.display()\n            ),\n        ));\n    }\n    if device_id(&metadata) != root_device || active_mountpoint(path)? {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\n                \"legacy audit tree crosses a filesystem or mount boundary: {}\",\n                path.display()\n            ),\n        ));\n    }\n    astrid_core::platform_fs::verify_no_redirects(path)?;\n    for entry in fs::read_dir(path).map_err(io::Error::other)? {","sourceCodeStart":496,"sourceCodeEnd":532,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L496-L532","documentation":"validate_audit_tree checks each node of the legacy audit tree (no-follow, via symlink_metadata) and rejects any node that is a symlink or not a directory with InvalidData. The migration barrier requires the audit tree to consist solely of plain directories and regular files; redirected nodes could point outside the tree and cause data outside the legacy layout to be renamed or deleted.","triggerScenarios":"Preflighting, retiring, or deleting a legacy audit tree while any node on the path (root or reached during recursion) is a symlink or a non-directory (file passed at directory level, fifo, etc.). Called from preflight_legacy_audit_sources, retire_legacy_audit_dir, validate_audit_tree (recursive), and delete_audit_tree.","commonSituations":"Dotfile managers symlinking parts of ~/.astrid; an 'audit' path that is actually a file; symlinked subdirectories inside the audit tree pointing to external storage.","solutions":["Replace every symlink inside (or at the root of) the audit tree with a real directory containing the data.","Remove or rename non-directory entries that occupy paths expected to be directories.","Re-run the migration after the tree contains only plain directories and regular files."],"exampleFix":"// before\nln -s /var/lib/audit ~/.astrid/principal/audit\n// after\nrm ~/.astrid/principal/audit\nmv /var/lib/audit ~/.astrid/principal/audit","handlingStrategy":"validation","validationCode":"fn audit_tree_is_plain(root: &Path) -> std::io::Result<bool> {\n    for entry in std::fs::read_dir(root)? {\n        let m = std::fs::symlink_metadata(entry?.path())?;\n        if m.file_type().is_symlink() { return Ok(false); }\n    }\n    Ok(true)\n}","typeGuard":"fn is_plain_tree_node(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir() && !m.file_type().is_symlink()).unwrap_or(false)\n}","tryCatchPattern":"if let Err(e) = migrate_legacy_audit(&home, &source) {\n    if e.to_string().contains(\"redirected or not a directory\") {\n        // dereference/replace symlinks, then retry\n    }\n}","preventionTips":["Keep dotfile managers away from ~/.astrid.","Run find <audit-dir> -type l before migrating and replace links with copies.","Document that the audit tree must contain only real dirs and files."],"tags":["filesystem","symlink","security","migration"],"backgroundTag":"incompatible-source-type","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"}