jdx/mise · error
recovery directory descends through a file or symlink
Error message
recovery directory descends through a file or symlink
What it means
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.
Source
Thrown at src/system/history/recovery.rs:181
if relative.as_os_str().is_empty()
|| !relative
.components()
.all(|c| matches!(c, Component::Normal(_)))
|| !seen.insert(relative)
{
bail!("invalid path inside recovery directory");
}
}
for leaf in files
.iter()
.map(|f| &f.rel)
.chain(links.iter().map(|f| &f.rel))
{
if seen
.iter()
.any(|other| *other != leaf && other.starts_with(leaf))
{
bail!("recovery directory descends through a file or symlink");
}
}
for file in files {
read_blob(state_dir, &file.content)?;
}
}
PathSnapshot::Unrecorded { reason, .. } => bail!("no usable preimage: {reason}"),
_ => {}
}
Ok(())
}
fn remove_leaf(path: &Path) -> Result<()> {
match std::fs::symlink_metadata(path) {
Ok(meta) if meta.is_dir() => std::fs::remove_dir(path)?,
Ok(_) => std::fs::remove_file(path)?,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error.into()),View on GitHub (pinned to afd2eddd3a)
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.
Example fix
// before (contradictory snapshot)
[{"rel": "settings", "type": "file"}, {"rel": "settings/app.conf", ...}]
// after (consistent tree)
[{"rel": "settings/app.conf", "type": "file"}] Defensive patterns
Strategy: validation
Validate before calling
// ensure no file/link rel is an ancestor of another entry
let rels: Vec<_> = files.iter().chain(links.iter()).map(|f| &f.rel).collect();
for leaf in &rels {
assert!(!rels.iter().any(|o| *o != *leaf && o.starts_with(*leaf)), "tree contradiction at {leaf:?}");
} Try / catch
match result { Err(e) if e.to_string().contains("descends through a file or symlink") => /* fix or discard the contradictory snapshot record */, other => other? } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- cannot safely change {} without a recovery preimage: {reason
- no usable preimage: {reason}
- remote rustc action metadata is invalid
- remote action output directory is invalid
- invalid task action identity
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/e1520cd875764c34.
Report an issue: GitHub.