jdx/mise · error · eyre::Report
refusing to replace non-directory path
Error message
refusing to replace non-directory path {}; set replace = true to allow replacement What it means
During planning of a managed file or directory, mise found the target path already exists but is not a directory (e.g. a regular file or symlink) while the declared state requires creating/managing it as a directory. Because replacing it would destroy existing content, mise refuses unless the config explicitly opts in with `replace = true`. This is a safety guard against clobbering unknown files at declared system paths.
Solutions
- If replacing the non-directory path is intended, set `replace = true` on the directory entry in [bootstrap.directories].
- Verify the path is the one you intended — check for typos before allowing replacement.
- Remove or relocate the conflicting file/symlink manually, or declare it in [bootstrap.files] with state = "absent" so mise cleans it up first.
- If the path should stay a file, move the config entry from [bootstrap.directories] to [bootstrap.files].
Example fix
# before [bootstrap.directories.tmp-build] path = "/var/lib/myapp/cache" # after [bootstrap.directories.tmp-build] path = "/var/lib/myapp/cache" replace = true
Defensive patterns
Strategy: validation
Validate before calling
import os
def ensure_replace_safe(path: str, replace: bool):
if os.path.lexists(path) and not os.path.isdir(path) and not replace:
raise ValueError(f"{path} exists and is not a directory; set replace = true or clean it up first") Prevention
- Before declaring a directory, check the existing node type on target machines (ls -ld).
- Set replace = true deliberately only for paths you own.
- Declare stale conflicting files in [bootstrap.files] with state = "absent".
When it happens
Trigger: A [bootstrap.directories] entry (state present) whose path already exists on disk as a regular file or symlink, with `replace` unset/false; `plan()` returns ResourceAction::Unknown for the path and the bail fires in the directory-ensure path.
Common situations: A package or earlier setup step created a file where the config now declares a directory (e.g. /etc/foo moved from file to directory upstream); a stale symlink left by a previous tool; retargeting an existing path by mistake (typo in the directory path).
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- refusing to manage the filesystem root
- [bootstrap].config_roots did not match any config roots
- bootstrap resource ' ' depends on missing resource
- bootstrap resource ' ' is declared more than once
- bootstrap users and groups are only supported on Linux
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/c66934c2503cb73c.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/managed_files.rs:740
inspection: None,
})
}
pub(crate) fn plan(&self) -> Result<ResourcePlan> {
plan_directory(self).map(|plan| {
plan.with_origin(self.origin.clone())
.with_file_phase(self.phase)
})
}
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 {
path: self.path.clone(),
recursive: self.recursive,
},
}))View on GitHub (pinned to 533346cc37)