{"record":{"id":"61bd24c61e5e28cf","repo":"jdx/mise","slug":"is-not-a-directory-resolve-its-type-before-pul","errorCode":null,"errorMessage":"{} is not a directory; resolve its type before pulling","messagePattern":"(.+?) is not a directory; resolve its type before pulling","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/directories.rs","lineNumber":23,"sourceCode":"\nuse crate::system::history::{\n    journal, manifest::Manifest, shadow::HistoryRepo, tracked::TrackedSet,\n};\n\n#[derive(Clone, Debug)]\npub(super) struct Step {\n    pub path: PathBuf,\n    before: Option<(u64, u64, u32)>,\n    desired: u32,\n    written: Option<(u64, u64, u32)>,\n}\n\n#[cfg(unix)]\nfn observe(path: &std::path::Path) -> Result<Option<(u64, u64, u32)>> {\n    use std::os::unix::fs::MetadataExt;\n    match std::fs::symlink_metadata(path) {\n        Ok(meta) if meta.is_dir() => Ok(Some((meta.dev(), meta.ino(), meta.mode() & 0o777))),\n        Ok(_) => bail!(\n            \"{} is not a directory; resolve its type before pulling\",\n            crate::file::display_path(path)\n        ),\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),\n        Err(err) => Err(err.into()),\n    }\n}\n\n#[cfg(not(unix))]\nfn observe(_path: &std::path::Path) -> Result<Option<(u64, u64, u32)>> {\n    Ok(None)\n}\n\npub(super) fn plan(repo: &HistoryRepo, tracked: &TrackedSet, tree: &str) -> Result<Vec<Step>> {\n    if !cfg!(unix) {\n        return Ok(vec![]);\n    }\n    let local = repo.ref_oid(HistoryRepo::HISTORY_REF)?;","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/sync/directories.rs#L5-L41","documentation":"observe() snapshots a directory's identity (dev, ino, mode) for permission planning. If the path exists but is not a directory (file or symlink), planning cannot proceed safely; the caller must resolve the path's type before a pull.","triggerScenarios":"A path expected to be a directory in setup history was replaced by a file or symlink; plan/validate/apply/verify_written encounter a non-directory at a tracked directory path.","commonSituations":"A sync or restore replaced the directory with a file; a user deleted the directory and created a file of the same name; a symlink pointing at a directory is treated per its own type.","solutions":["Check the path (`ls -la <path>`); if it should be a directory, remove the file/symlink and recreate the directory, then retry the pull.","If the path should no longer be a directory, update the setup-history configuration to stop tracking it as one.","Resolve the path type in your setup before running pull (e.g. ensure the real directory exists)."],"exampleFix":"// before\n~/.config/app  (regular file)\n// after\nrm ~/.config/app && mkdir ~/.config/app","handlingStrategy":"validation","validationCode":"let meta = std::fs::symlink_metadata(path)?;\nif !meta.is_dir() {\n    // recreate as a directory before pulling\n}","typeGuard":"fn is_directory(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Don't replace tracked directories with files or symlinks","Recreate directories with mkdir after accidental deletion","Verify path types before restores or imports"],"tags":["filesystem","directory","type","sync"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}