{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/managed_files.rs","lineNumber":1376,"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":1358,"sourceCodeEnd":1394,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/managed_files.rs#L1358-L1394","documentation":"write_file() builds the complete replacement in a temp file, then atomically persists it over the target. Before that, symlink_metadata on the destination must be absent or a regular file. Any other existing type (symlink, directory, fifo, socket) with replace not enabled is refused. With replace = true, directories are removed via remove_dir (non-empty directories fail with a wrapped error) and other non-files via remove_file.","triggerScenarios":"The target path is currently a symlink (symlink_metadata sees the link itself, not its target) or a directory, and the [bootstrap.files] entry does not set replace = true.","commonSituations":"System path already managed by /etc/alternatives, a dotfiles manager, or another config tool via symlink; a directory sits where a file is declared.","solutions":["Set replace = true on the entry so mise removes the non-file and puts the managed file in place","Remove the symlink/directory yourself if you want to keep replace off as a safety stance","If it is a directory, make sure it is empty - replacing a non-empty directory with a file fails by design"],"exampleFix":"# before: /etc/app/app.conf is currently a symlink -> error\n[bootstrap.files]\n\"/etc/app/app.conf\" = { content = \"...\", mode = \"0644\" }\n\n# after\n[bootstrap.files]\n\"/etc/app/app.conf\" = { content = \"...\", mode = \"0644\", replace = true }","handlingStrategy":"validation","validationCode":"match std::fs::symlink_metadata(path) {\n    Ok(m) if !m.file_type().is_file() && !replace => {\n        return Err(eyre::eyre!(\"destination is a non-file; set replace = true\"));\n    }\n    _ => {}\n}","typeGuard":"fn can_write_without_replace(path: &std::path::Path) -> bool {\n    match std::fs::symlink_metadata(path) {\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,\n        Ok(m) => m.file_type().is_file(),\n        _ => false,\n    }\n}","tryCatchPattern":"match write_file(path, content, owner, group, mode, replace) {\n    Err(e) if e.to_string().contains(\"refusing to replace non-file path\") => {\n        // decide explicitly: set replace = true in config, or remove the symlink/dir manually\n    }\n    other => other?,\n}","preventionTips":["Audit existing symlinks/dirs at target paths before declaring managed files","Set replace = true only where takeover is intended; leave it off as a safety default"],"tags":["filesystem","replace","symlink","safety","managed-files"],"backgroundTag":"refusing-to-replace-existing-path","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}