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

  1. Set `replace: true` if the non-file path should indeed be replaced (empty directories are removed; non-empty ones still fail)
  2. Remove or rename the conflicting non-file path manually
  3. 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

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


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)