jdx/mise · error · eyre::Report
refusing to replace non-file path
Error message
refusing to replace non-file path: {} What it means
When writing a managed file, the destination is checked via `fs::symlink_metadata`. If the destination exists as something other than a regular file (directory, symlink, fifo, etc.) and the caller did not request replacement (`replace == false`), the library refuses to clobber it. This protects non-file entries from being destroyed by a file write.
Solutions
- Set `replace: true` if the non-file path should indeed be replaced (empty directories are removed; non-empty ones still fail)
- Remove or rename the conflicting non-file path manually
- Correct the manifest so the path's declared kind matches what exists on disk
Example fix
// before write_managed(path, &content, ReplaceMode::Keep); // path is a directory // after write_managed(path, &content, ReplaceMode::Replace); // allows removing empty dir first
Defensive patterns
Strategy: validation
Validate before calling
fn check_dest_kind(path: &std::path::Path, replace: bool) -> Result<(), String> {
match std::fs::symlink_metadata(path) {
Err(_) => Ok(()), // not found is fine
Ok(m) if m.file_type().is_file() => Ok(()),
Ok(_) if replace => Ok(()),
Ok(_) => Err(format!("{} exists and is not a file; pass replace", path.display())),
}
} Type guard
fn dest_is_file_or_absent(path: &std::path::Path) -> bool {
std::fs::symlink_metadata(path).map_or(true, |m| m.file_type().is_file())
} Try / catch
match result {
Err(e) if e.to_string().contains("refusing to replace non-file") => {
// inspect the path, clean it up, then retry with replace=true if safe
}
other => other?,
} Prevention
- Reconcile manifest-declared path kinds with on-disk reality before runs
- Avoid symlinking managed file destinations
- Decide replace policy explicitly per managed path
When it happens
Trigger: Calling a managed-file install with `replace: false` on a path that already exists as a directory or symlink — e.g. path "/etc/app" where "/etc/app" is a directory but the manifest defines it as a file.
Common situations: Schema drift between manifest and filesystem (a path changed kind between tool versions); user manually created a directory where the tool expects to place a file; symlinked locations.
Related errors
- cannot be recreated as a directory: something else is there…
- changed during recovery; left untouched
- refusing to remove non-directory path
- refusing to replace non-directory path
- refusing to resolve managed directory
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/df02bd570aca6da7.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/managed_files.rs:1553
parent.display()
),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
bail!("managed file parent does not exist: {}", parent.display())
}
Err(error) => return Err(error.into()),
}
// Prepare the complete replacement before mutating the destination. In
// particular, a metadata permission error must leave the old path intact.
let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
temporary.write_all(content)?;
set_metadata(temporary.path(), owner, group, mode)?;
temporary.as_file_mut().sync_all()?;
match fs::symlink_metadata(path) {
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error.into()),
Ok(metadata) if metadata.file_type().is_file() => {}
Ok(_) if !replace => {
bail!("refusing to replace non-file path: {}", path.display())
}
Ok(metadata) if metadata.file_type().is_dir() => {
fs::remove_dir(path).wrap_err_with(|| {
format!(
"refusing to replace non-empty directory with file: {}",
path.display()
)
})?
}
Ok(_) => fs::remove_file(path)?,
}
temporary
.persist(path)
.map_err(|error| error.error)
.wrap_err_with(|| format!("failed to atomically replace {}", path.display()))?;
fs::File::open(parent)?.sync_all()?;
Ok(())
}View on GitHub (pinned to 533346cc37)