{"record":{"id":"b1cd6ae62ad103c2","repo":"astrid-runtime/astrid","slug":"legacy-source-contains-redirect-or-special-entry","errorCode":null,"errorMessage":"legacy source contains redirect or special entry: {}","messagePattern":"legacy source contains redirect or special entry: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":286,"sourceCode":"            io::ErrorKind::InvalidData,\n            format!(\"legacy source is an active mount: {}\", path.display()),\n        ));\n    }\n    let device = device_id(&metadata);\n    for entry in fs::read_dir(path).map_err(io::Error::other)? {\n        let child = entry.map_err(io::Error::other)?.path();\n        if protected.iter().any(|candidate| candidate == &child) {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy component source reappeared during ordinary retirement: {}\",\n                    child.display()\n                ),\n            ));\n        }\n        let child_meta = fs::symlink_metadata(&child).map_err(io::Error::other)?;\n        if child_meta.file_type().is_symlink() || (!child_meta.is_file() && !child_meta.is_dir()) {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy source contains redirect or special entry: {}\",\n                    child.display()\n                ),\n            ));\n        }\n        if active_mountpoint(&child)? || device_id(&child_meta) != device {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy source crosses a mount or device boundary: {}\",\n                    child.display()\n                ),\n            ));\n        }\n        if child_meta.is_dir() {\n            let child_snapshot = snapshot_path(&child)?;","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L268-L304","documentation":"A child of the legacy tree being retired is a symlink or a special file (FIFO, socket, device). retire_tree only deletes plain files and directories; anything that could redirect the walk or have side effects on open is rejected with InvalidData, naming the offending child.","triggerScenarios":"retire_tree (and retire_leaf's re-check) calls symlink_metadata on each child; `child_meta.file_type().is_symlink() || (!is_file() && !is_dir())` matches — a symlink swapped in for a regular file, a leftover socket/FIFO from a legacy daemon, or a device node. A second check in retire_leaf catches entries swapped between the directory scan and unlink.","commonSituations":"Legacy daemons leaving Unix sockets inside their state dirs; users symlinking large files into the legacy tree to save space; backup/sync tools creating hardlink-like shortcuts; compromised or tampered trees.","solutions":["Remove or replace the reported symlink/special entry: `rm` the symlink/FIFO/socket, then `cp -a` the real file if one is needed.","Stop any legacy process that may own the socket/FIFO before removing it.","Verify the tree contains only regular files and directories (`find <path> ! -type f ! -type d`) before rerunning migration.","Rerun the migration; the check is per-entry, so clean all offenders the message reports."],"exampleFix":"# before\nln -s /mnt/big/archive.dat ~/.astrid/legacy/db/archive.dat\n// after\nrm ~/.astrid/legacy/db/archive.dat\ncp -a /mnt/big/archive.dat ~/.astrid/legacy/db/archive.dat","handlingStrategy":"validation","validationCode":"use std::fs;\nfn tree_only_regular_entries(path: &std::path::Path) -> std::io::Result<Vec<std::path::PathBuf>> {\n    let mut bad = Vec::new();\n    for entry in walkdir_like(path) {\n        let m = fs::symlink_metadata(&entry)?;\n        if m.file_type().is_symlink() || (!m.is_file() && !m.is_dir()) {\n            bad.push(entry);\n        }\n    }\n    Ok(bad) // must be empty before migration\n}","typeGuard":"fn is_plain_entry(m: &std::fs::Metadata) -> bool {\n    !m.file_type().is_symlink() && (m.is_file() || m.is_dir())\n}","tryCatchPattern":"match retire_tree(path, &expected, &protected) {\n    Err(e) if e.to_string().contains(\"redirect or special entry\") => {\n        let child = extract_path(&e.to_string());\n        eprintln!(\"remove or replace {child} with a regular file\");\n    }\n    other => other?,\n}","preventionTips":["Pre-scan with `find <legacy-path> ! -type f ! -type d` and clean all hits before migrating","Stop legacy daemons so they stop recreating sockets/FIFOs in the tree","Never symlink large files into the legacy tree to save space during migration"],"tags":["filesystem","symlink","migration","rust"],"backgroundTag":"path-traversal-blocked","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"}