{"record":{"id":"df02bd570aca6da7","repo":"jdx/mise","slug":"refusing-to-replace-non-file-path","errorCode":null,"errorMessage":"refusing to replace non-file path: {}","messagePattern":"refusing to replace non-file path: (.+?)","errorType":"error_code","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/managed_files.rs","lineNumber":1553,"sourceCode":"            parent.display()\n        ),\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {\n            bail!(\"managed file parent does not exist: {}\", parent.display())\n        }\n        Err(error) => return Err(error.into()),\n    }\n    // Prepare the complete replacement before mutating the destination. In\n    // particular, a metadata permission error must leave the old path intact.\n    let mut temporary = tempfile::NamedTempFile::new_in(parent)?;\n    temporary.write_all(content)?;\n    set_metadata(temporary.path(), owner, group, mode)?;\n    temporary.as_file_mut().sync_all()?;\n    match fs::symlink_metadata(path) {\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}\n        Err(error) => return Err(error.into()),\n        Ok(metadata) if metadata.file_type().is_file() => {}\n        Ok(_) if !replace => {\n            bail!(\"refusing to replace non-file path: {}\", path.display())\n        }\n        Ok(metadata) if metadata.file_type().is_dir() => {\n            fs::remove_dir(path).wrap_err_with(|| {\n                format!(\n                    \"refusing to replace non-empty directory with file: {}\",\n                    path.display()\n                )\n            })?\n        }\n        Ok(_) => fs::remove_file(path)?,\n    }\n    temporary\n        .persist(path)\n        .map_err(|error| error.error)\n        .wrap_err_with(|| format!(\"failed to atomically replace {}\", path.display()))?;\n    fs::File::open(parent)?.sync_all()?;\n    Ok(())\n}","sourceCodeStart":1535,"sourceCodeEnd":1571,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/managed_files.rs#L1535-L1571","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nwrite_managed(path, &content, ReplaceMode::Keep); // path is a directory\n// after\nwrite_managed(path, &content, ReplaceMode::Replace); // allows removing empty dir first","handlingStrategy":"validation","validationCode":"fn check_dest_kind(path: &std::path::Path, replace: bool) -> Result<(), String> {\n    match std::fs::symlink_metadata(path) {\n        Err(_) => Ok(()), // not found is fine\n        Ok(m) if m.file_type().is_file() => Ok(()),\n        Ok(_) if replace => Ok(()),\n        Ok(_) => Err(format!(\"{} exists and is not a file; pass replace\", path.display())),\n    }\n}","typeGuard":"fn dest_is_file_or_absent(path: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(path).map_or(true, |m| m.file_type().is_file())\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"refusing to replace non-file\") => {\n        // inspect the path, clean it up, then retry with replace=true if safe\n    }\n    other => other?,\n}","preventionTips":["Reconcile manifest-declared path kinds with on-disk reality before runs","Avoid symlinking managed file destinations","Decide replace policy explicitly per managed path"],"tags":["filesystem","path-conflict","safety"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}