{"record":{"id":"1394bc36f31e5170","repo":"jdx/mise","slug":"refusing-to-remove-directory-as-a-file","errorCode":null,"errorMessage":"refusing to remove directory as a file: {}","messagePattern":"refusing to remove directory as a file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/managed_files.rs","lineNumber":1401,"sourceCode":"                )\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}\n\nfn remove_file(path: &Path) -> Result<()> {\n    match fs::symlink_metadata(path) {\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),\n        Err(err) => Err(err.into()),\n        Ok(metadata) if metadata.file_type().is_dir() => {\n            bail!(\"refusing to remove directory as a file: {}\", path.display())\n        }\n        Ok(_) => fs::remove_file(path).map_err(Into::into),\n    }\n}\n\nfn create_directory(\n    path: &Path,\n    owner: Option<&str>,\n    group: Option<&str>,\n    mode: u32,\n    replace: bool,\n) -> Result<()> {\n    match fs::symlink_metadata(path) {\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => fs::create_dir(path)?,\n        Err(error) => return Err(error.into()),\n        Ok(metadata) if metadata.file_type().is_dir() => {}\n        Ok(_) if !replace => {\n            bail!(\"refusing to replace non-directory path: {}\", path.display())","sourceCodeStart":1383,"sourceCodeEnd":1419,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/managed_files.rs#L1383-L1419","documentation":"For a managed file with state = \"absent\", remove_file() refuses when the path currently exists but is a directory. Removing it would require directory semantics (and possibly recursion) that the file entry does not declare, so the resource type and on-disk type must agree.","triggerScenarios":"A [bootstrap.files] entry with state = \"absent\" whose path is actually a directory on disk.","commonSituations":"A path changed roles over time (was a file, later became a directory); stale absent declarations after restructuring config.","solutions":["Declare the removal under [bootstrap.directories] with state = \"absent\" (and recursive = true if it may be non-empty)","Or remove the directory manually (rmdir / rm -r) and let the file entry find nothing to do"],"exampleFix":"# before: /opt/app/legacy.conf is actually a directory\n[bootstrap.files]\n\"/opt/app/legacy.conf\" = { state = \"absent\" }\n\n# after\n[bootstrap.directories]\n\"/opt/app/legacy.conf\" = { state = \"absent\", recursive = true }","handlingStrategy":"validation","validationCode":"if state == ManagedState::Absent {\n    if let Ok(m) = std::fs::symlink_metadata(path) {\n        if m.file_type().is_dir() {\n            return Err(eyre::eyre!(\"path is a directory; declare it under [bootstrap.directories]\"));\n        }\n    }\n}","typeGuard":"fn removal_kind_matches(path: &std::path::Path, expect_dir: bool) -> bool {\n    match std::fs::symlink_metadata(path) {\n        Err(_) => true,\n        Ok(m) => m.file_type().is_dir() == expect_dir,\n    }\n}","tryCatchPattern":null,"preventionTips":["Match the resource table (files vs directories) to what actually sits on disk","Re-check absent declarations after restructuring paths"],"tags":["filesystem","path-type","removal","managed-files"],"backgroundTag":"path-type-mismatch","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}