jdx/mise · error

{} is not a directory; resolve its type before pulling

Error message

{} is not a directory; resolve its type before pulling

What it means

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.

Source

Thrown at src/system/history/sync/directories.rs:23

use crate::system::history::{
    journal, manifest::Manifest, shadow::HistoryRepo, tracked::TrackedSet,
};

#[derive(Clone, Debug)]
pub(super) struct Step {
    pub path: PathBuf,
    before: Option<(u64, u64, u32)>,
    desired: u32,
    written: Option<(u64, u64, u32)>,
}

#[cfg(unix)]
fn observe(path: &std::path::Path) -> Result<Option<(u64, u64, u32)>> {
    use std::os::unix::fs::MetadataExt;
    match std::fs::symlink_metadata(path) {
        Ok(meta) if meta.is_dir() => Ok(Some((meta.dev(), meta.ino(), meta.mode() & 0o777))),
        Ok(_) => bail!(
            "{} is not a directory; resolve its type before pulling",
            crate::file::display_path(path)
        ),
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
        Err(err) => Err(err.into()),
    }
}

#[cfg(not(unix))]
fn observe(_path: &std::path::Path) -> Result<Option<(u64, u64, u32)>> {
    Ok(None)
}

pub(super) fn plan(repo: &HistoryRepo, tracked: &TrackedSet, tree: &str) -> Result<Vec<Step>> {
    if !cfg!(unix) {
        return Ok(vec![]);
    }
    let local = repo.ref_oid(HistoryRepo::HISTORY_REF)?;

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Check the path (`ls -la <path>`); if it should be a directory, remove the file/symlink and recreate the directory, then retry the pull.
  2. If the path should no longer be a directory, update the setup-history configuration to stop tracking it as one.
  3. Resolve the path type in your setup before running pull (e.g. ensure the real directory exists).

Example fix

// before
~/.config/app  (regular file)
// after
rm ~/.config/app && mkdir ~/.config/app
Defensive patterns

Strategy: validation

Validate before calling

let meta = std::fs::symlink_metadata(path)?;
if !meta.is_dir() {
    // recreate as a directory before pulling
}

Type guard

fn is_directory(p: &Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_dir()).unwrap_or(false)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/61bd24c61e5e28cf. Report an issue: GitHub.