jdx/mise · error

refusing to remove non-directory path {} as a directory; dec

Error message

refusing to remove non-directory path {} as a directory; declare it in [bootstrap.files]

What it means

Raised in ManagedDirectoryRequest::operation when a directory entry with state="absent" is planned and on-disk inspection returns ResourceAction::Unknown because the target path is actually a regular file (or other non-directory). The library refuses to delete a file through the directory resource; it must be declared under [bootstrap.files] instead.

Source

Thrown at src/system/managed_files.rs:668

            group: nonempty("group", config.group)?,
            mode: parse_mode(config.mode.as_deref(), 0o755)?,
            state: config.state,
            recursive: config.recursive,
            replace: config.replace,
            notify: config.notify,
            origin,
            inspection: None,
        })
    }

    pub(crate) fn plan(&self) -> Result<ResourcePlan> {
        plan_directory(self).map(|plan| plan.with_origin(self.origin.clone()))
    }

    fn operation(&self) -> Result<Option<PrivilegedAction>> {
        match self.plan()?.action {
            ResourceAction::Noop => return Ok(None),
            ResourceAction::Unknown if self.state == ManagedState::Absent => bail!(
                "refusing to remove non-directory path {} as a directory; declare it in [bootstrap.files]",
                self.path.display()
            ),
            ResourceAction::Unknown => bail!(
                "refusing to replace non-directory path {}; set replace = true to allow replacement",
                self.path.display()
            ),
            _ => {}
        }
        Ok(Some(match self.state {
            ManagedState::Present => PrivilegedAction::CreateDirectory {
                path: self.path.clone(),
                owner: self.owner.clone(),
                group: self.group.clone(),
                mode: self.mode,
                replace: self.replace,
            },
            ManagedState::Absent => PrivilegedAction::RemoveDirectory {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Move the entry from [bootstrap.directories] to [bootstrap.files] with state = "absent" so it is removed as a file.
  2. Or delete the file manually and re-run bootstrap.
  3. If the path should exist, correct the entry's state/target rather than removing it.

Example fix

# before
[[bootstrap.directories]]
path = "/etc/myapp/legacy"
state = "absent"

# after
[[bootstrap.files]]
path = "/etc/myapp/legacy"
state = "absent"
Defensive patterns

Strategy: validation

Validate before calling

for path in &declared_absent_dirs {
    if let Ok(meta) = std::fs::metadata(path) {
        if !meta.is_dir() {
            eprintln!("{} is not a directory; declare under [bootstrap.files]", path.display());
        }
    }
}

Type guard

fn is_directory(path: &std::path::Path) -> bool {
    std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(true)
}

Prevention

When it happens

Trigger: Converging a [bootstrap.directories] entry with state = "absent" whose path exists on disk as a non-directory (plan_directory returns ResourceAction::Unknown), hitting the (Unknown, Absent) arm.

Common situations: An app replaced a former directory with a plain file (marker file, symlink change); the config author assumed the path was a directory; state drift where a previous bootstrap version wrote a file at that path.

Related errors


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