{"record":{"id":"b3da9caa20ac300f","repo":"astrid-runtime/astrid","slug":"legacy-audit-tree-is-redirected-or-not-a-directory-b3da9c","errorCode":null,"errorMessage":"legacy audit tree is redirected or not a directory: {path}","messagePattern":"legacy audit tree is redirected or not a directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":4322,"sourceCode":"            }\n        },\n        Err(error) => return Err(error),\n    }\n    Ok(())\n}\n\n#[cfg(unix)]\nfn audit_tree_device(metadata: &std::fs::Metadata) -> u64 {\n    use std::os::unix::fs::MetadataExt as _;\n\n    metadata.dev()\n}\n\n#[cfg(unix)]\nfn validate_audit_tree(path: &Path, root_device: u64) -> std::io::Result<()> {\n    let metadata = std::fs::symlink_metadata(path)?;\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            format!(\n                \"legacy audit tree is redirected or not a directory: {}\",\n                path.display()\n            ),\n        ));\n    }\n    if audit_tree_device(&metadata) != root_device || audit_mountpoint(path)? {\n        return Err(std::io::Error::new(\n            std::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 std::fs::read_dir(path)? {","sourceCodeStart":4304,"sourceCodeEnd":4340,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L4304-L4340","documentation":"validate_audit_tree recursively re-checks every node of the audit tree before and after the retirement rename. Any node that is a symlink or not a directory means part of the tree could redirect writes outside the validated device, so the whole tree is rejected with InvalidData.","triggerScenarios":"Retiring the legacy audit tree when any subdirectory inside it is a symlink, or when a file exists where a directory is expected in the audit hierarchy.","commonSituations":"Admins symlinking a large audit subdirectory to another disk; corrupted/partial audit trees after crashes; backup-restore tools replacing directories with links.","solutions":["Find the offending node (walk the tree with `find <path> -type l -o ! -type d`) and replace the symlink/file with a real directory","Materialize symlinked subdirectories back into the tree (mv the target content in place)","Repair or recreate the audit tree from logs if it is structurally corrupted"],"exampleFix":"// before\naudit/shard-3 -> /mnt/spare/shard-3   (symlink inside tree)\n// after\nrm audit/shard-3 && mv /mnt/spare/shard-3 audit/shard-3","handlingStrategy":"validation","validationCode":"fn tree_has_no_symlinks(root: &std::path::Path) -> std::io::Result<bool> {\n    for entry in walkdir_like(root)? {\n        let m = std::fs::symlink_metadata(&entry)?;\n        if m.file_type().is_symlink() { return Ok(false); }\n    }\n    Ok(true)\n}","typeGuard":"fn is_real_directory(p: &std::path::Path) -> bool {\n    matches!(std::fs::symlink_metadata(p), Ok(m) if m.is_dir() && !m.file_type().is_symlink())\n}","tryCatchPattern":"if let Err(e) = run_migration() {\n    if e.kind() == std::io::ErrorKind::InvalidData\n        && e.to_string().contains(\"redirected or not a directory\") {\n        // locate offending node from the message path, materialize it, retry\n    } else { return Err(e); }\n}","preventionTips":["Run `find <audit_root> -type l` as a pre-flight check before upgrading","Ban symlinks inside the audit tree by policy","After restoring backups, verify no links were substituted for directories"],"tags":["filesystem","symlink","audit","rust"],"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"}