{"record":{"id":"e1520cd875764c34","repo":"jdx/mise","slug":"recovery-directory-descends-through-a-file-or-syml","errorCode":null,"errorMessage":"recovery directory descends through a file or symlink","messagePattern":"recovery directory descends through a file or symlink","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/recovery.rs","lineNumber":181,"sourceCode":"                if relative.as_os_str().is_empty()\n                    || !relative\n                        .components()\n                        .all(|c| matches!(c, Component::Normal(_)))\n                    || !seen.insert(relative)\n                {\n                    bail!(\"invalid path inside recovery directory\");\n                }\n            }\n            for leaf in files\n                .iter()\n                .map(|f| &f.rel)\n                .chain(links.iter().map(|f| &f.rel))\n            {\n                if seen\n                    .iter()\n                    .any(|other| *other != leaf && other.starts_with(leaf))\n                {\n                    bail!(\"recovery directory descends through a file or symlink\");\n                }\n            }\n            for file in files {\n                read_blob(state_dir, &file.content)?;\n            }\n        }\n        PathSnapshot::Unrecorded { reason, .. } => bail!(\"no usable preimage: {reason}\"),\n        _ => {}\n    }\n    Ok(())\n}\n\nfn remove_leaf(path: &Path) -> Result<()> {\n    match std::fs::symlink_metadata(path) {\n        Ok(meta) if meta.is_dir() => std::fs::remove_dir(path)?,\n        Ok(_) => std::fs::remove_file(path)?,\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}\n        Err(error) => return Err(error.into()),","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/recovery.rs#L163-L199","documentation":"The Dir snapshot validation ensures no file or symlink entry is also an ancestor directory of another entry — otherwise restoring would need to create a file where a directory must exist (or vice versa). This bail means the recorded tree is self-contradictory: some recorded leaf path is claimed to contain other entries.","triggerScenarios":"validate_snapshot finds a `rel` in files ∪ links such that another seen entry starts_with that path (e.g. both `a` recorded as a file and `a/b` recorded as a file beneath it).","commonSituations":"Corrupted or hand-edited snapshot JSON where an entry's type flipped between file and directory, or a state where the file type changed on disk between capture and journal write.","solutions":["Inspect the pending-operation JSON and resolve the conflicting entry (remove the file entry whose path is also a parent of another entry).","Discard the corrupt record and accept current contents, restoring the tree manually.","If reproducible without manual edits, report a capture bug: snapshot should never record a file and its descendant simultaneously."],"exampleFix":"// before (contradictory snapshot)\n[{\"rel\": \"settings\", \"type\": \"file\"}, {\"rel\": \"settings/app.conf\", ...}]\n\n// after (consistent tree)\n[{\"rel\": \"settings/app.conf\", \"type\": \"file\"}]","handlingStrategy":"validation","validationCode":"// ensure no file/link rel is an ancestor of another entry\nlet rels: Vec<_> = files.iter().chain(links.iter()).map(|f| &f.rel).collect();\nfor leaf in &rels {\n    assert!(!rels.iter().any(|o| *o != *leaf && o.starts_with(*leaf)), \"tree contradiction at {leaf:?}\");\n}","typeGuard":null,"tryCatchPattern":"match result { Err(e) if e.to_string().contains(\"descends through a file or symlink\") => /* fix or discard the contradictory snapshot record */, other => other? }","preventionTips":["Avoid changing a path between file and directory while a write operation is in flight","Let interrupted operations be resolved via recovery before restructuring the tree","Back up snapshot JSON before attempting manual repairs"],"tags":["snapshot","validation","filesystem-tree","recovery"],"backgroundTag":"schema-validation-failed","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}