jdx/mise · error
refusing to remove directory {} as a file; declare it in [bo
Error message
refusing to remove directory {} as a file; declare it in [bootstrap.directories] What it means
Raised in ManagedFileRequest::operation when a file entry with state="absent" is planned and on-disk inspection returns ResourceAction::Unknown because the target path is actually a directory. The library refuses to recursively delete a directory via the file resource; the user must declare it under [bootstrap.directories] instead.
Source
Thrown at src/system/managed_files.rs:596
owner,
group,
mode,
state: config.state,
replace: config.replace,
notify: config.notify,
origin,
inspection: None,
})
}
pub(crate) fn plan(&self) -> Result<ResourcePlan> {
plan_file(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 directory {} as a file; declare it in [bootstrap.directories]",
self.path.display()
),
ResourceAction::Unknown => bail!(
"refusing to replace non-file path {}; set replace = true to allow replacement",
self.path.display()
),
_ => {}
}
Ok(Some(match self.state {
ManagedState::Present => PrivilegedAction::WriteFile {
path: self.path.clone(),
content: self.content.clone().expect("present file has content"),
owner: self.owner.clone(),
group: self.group.clone(),
mode: self.mode,
replace: self.replace,
},View on GitHub (pinned to afd2eddd3a)
Solutions
- Move the entry from [bootstrap.files] to [bootstrap.directories] with state = "absent" so it is removed as a directory.
- Or remove the directory manually first, then run bootstrap again.
- If the directory should stay, drop the entry or change state to "present" under [bootstrap.directories].
Example fix
# before [[bootstrap.files]] path = "/var/lib/myapp/old-plugin" state = "absent" # after [[bootstrap.directories]] path = "/var/lib/myapp/old-plugin" state = "absent" recursive = true
Defensive patterns
Strategy: validation
Validate before calling
// before running bootstrap, check declared-absent file paths on the host
for path in &declared_absent_files {
if path.is_dir() {
eprintln!("{} is a directory; declare under [bootstrap.directories]", path.display());
}
} Type guard
fn is_directory(path: &std::path::Path) -> bool {
std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
} Prevention
- Before declaring a path absent as a file, stat it on target hosts to confirm it is a regular file.
- Keep directory-type resources under [bootstrap.directories] and file types under [bootstrap.files].
- After app upgrades that change file/dir layouts, audit bootstrap entries for type drift.
When it happens
Trigger: Converging a [bootstrap.files] entry with state = "absent" whose path exists on disk as a directory (plan_file cannot classify it as a regular file), producing the (ResourceAction::Unknown, Absent) arm.
Common situations: A directory was created at a path previously occupied by a file (app version change); the config author assumed the path was a file; migrating config where an old symlink target became a directory; tests running on hosts with different layouts.
Related errors
- refusing to remove non-directory path {} as a directory; dec
- refusing to replace non-file path {}; set replace = true to
- refusing to replace non-directory path {}; set replace = tru
- refusing unsafe change to bootstrap user '{}'; inspect `mise
- refusing unsafe change to bootstrap compose project '{}'; in
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/e6a887e5c3121117.
Report an issue: GitHub.