{"record":{"id":"b57d6eddd0803db7","repo":"astrid-runtime/astrid","slug":"legacy-state-source-is-redirected","errorCode":null,"errorMessage":"legacy state source is redirected: {}","messagePattern":"legacy state source is redirected: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_retirement.rs","lineNumber":43,"sourceCode":"    validate_legacy_tree(path, legacy_tree_device(&metadata))\n}\n\n#[cfg(unix)]\npub(super) fn legacy_tree_device(metadata: &std::fs::Metadata) -> u64 {\n    use std::os::unix::fs::MetadataExt as _;\n\n    metadata.dev()\n}\n\n#[cfg(not(unix))]\npub(super) fn legacy_tree_device(_metadata: &std::fs::Metadata) -> u64 {\n    0\n}\n\npub(super) fn validate_legacy_tree(path: &Path, root_device: u64) -> io::Result<()> {\n    let metadata = std::fs::symlink_metadata(path)?;\n    if metadata.file_type().is_symlink() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"legacy state source is redirected: {}\", path.display()),\n        ));\n    }\n    if !metadata.is_dir() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"legacy state source is not a directory: {}\", path.display()),\n        ));\n    }\n    crate::platform_fs::verify_no_redirects(path)?;\n    ensure_legacy_tree_boundary(path, root_device, &metadata)?;\n\n    for entry in std::fs::read_dir(path)? {\n        let entry = entry?;\n        let child = entry.path();\n        let child_metadata = std::fs::symlink_metadata(&child)?;\n        if child_metadata.file_type().is_symlink() {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_retirement.rs#L25-L61","documentation":"This error is thrown during no-follow retirement of the legacy directory-backed state tree when the source path itself is a symlink. The library deliberately refuses to operate through symlinks so a malicious or accidental redirection cannot cause validation or deletion to act on a different tree. It reports InvalidData with the offending path.","triggerScenarios":"Calling retire_legacy_source_tree or validate_legacy_retirement_candidate where the legacy state source path has been replaced by (or was created as) a symlink; symlink_metadata reports a symlink file type at validate_legacy_tree. Also reachable when an interior directory was swapped for a symlink between listing and recursion (validate_legacy_tree recursing into a child).","commonSituations":"Operators replacing the legacy state directory with a symlink to shared/network storage; packaging or deployment tooling that creates convenience symlinks into state directories; restore scripts that link rather than move data back; concurrent tampering during retirement.","solutions":["Remove the symlink and place the real directory (or a copy/move of its contents) at the legacy state source path, then retry retirement","Investigate who created the link: check deployment scripts, backup/restore tooling, and container image steps for symlink creation at that path","If the tree was legitimately moved, update the library's configured state path instead of linking the old location","Re-run retirement; the operation is idempotent and validation fails fast before anything is deleted"],"exampleFix":"// before: legacy state path is a symlink\nln -s /mnt/shared/legacy-state /srv/astrid/var/state\n// after: use a real directory (bind mount or move, not a symlink)\nmv /mnt/shared/legacy-state /srv/astrid/var/state   # or mount --bind","handlingStrategy":"validation","validationCode":"fn is_real_dir(path: &Path) -> std::io::Result<bool> {\n    let meta = std::fs::symlink_metadata(path)?;\n    Ok(meta.is_dir() && !meta.file_type().is_symlink())\n}\n// call before retirement: if !is_real_dir(&legacy_path)? { fix layout first }","typeGuard":"fn is_symlink(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|m| m.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":"match retire_legacy_source_tree(&path) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains(\"redirected\") => {\n        // resolve the symlink, restore a real directory, retry\n    }\n    other => other?,\n}","preventionTips":["Never symlink state directories; use bind mounts or update the configured path instead","Audit deployment and restore scripts for ln -s targeting the state tree","Check with symlink_metadata (not metadata) when pre-validating state paths so links are caught"],"tags":["rust","io","symlink","filesystem","security"],"backgroundTag":"invalid-argument-value","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"}