{"record":{"id":"fefaea9852898401","repo":"jdx/mise","slug":"refusing-to-remove-non-directory-path","errorCode":null,"errorMessage":"refusing to remove non-directory path: {}","messagePattern":"refusing to remove non-directory path: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/managed_files.rs","lineNumber":1434,"sourceCode":"        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())\n        }\n        Ok(_) => {\n            fs::remove_file(path)?;\n            fs::create_dir(path)?;\n        }\n    }\n    set_metadata(path, owner, group, mode)\n}\n\nfn remove_directory(path: &Path, recursive: bool) -> 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 non-directory path: {}\", path.display())\n        }\n        Ok(_) if recursive => fs::remove_dir_all(path).map_err(Into::into),\n        Ok(_) => fs::remove_dir(path).map_err(Into::into),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn file(path: &str, state: ManagedState) -> ManagedFileRequest {\n        ManagedFileRequest {\n            path: PathBuf::from(path),\n            content: (state == ManagedState::Present).then(|| \"content\".to_string()),\n            owner: None,\n            group: None,\n            mode: 0o644,\n            state,","sourceCodeStart":1416,"sourceCodeEnd":1452,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/managed_files.rs#L1416-L1452","documentation":"For a managed directory with state = \"absent\", remove_directory() refuses when the path exists but is not a directory (e.g. a regular file or symlink). The declared resource type and the on-disk type disagree, so removal is rejected; recursive only distinguishes remove_dir_all vs remove_dir for real directories.","triggerScenarios":"A [bootstrap.directories] entry with state = \"absent\" whose path is actually a regular file or symlink on disk.","commonSituations":"A path repurposed from directory to file between config revisions; stale directory-removal declarations.","solutions":["Declare it under [bootstrap.files] with state = \"absent\" instead, matching the actual type","Or remove the file manually and re-run"],"exampleFix":"# before: /opt/app/cache is actually a regular file\n[bootstrap.directories]\n\"/opt/app/cache\" = { state = \"absent\" }\n\n# after\n[bootstrap.files]\n\"/opt/app/cache\" = { state = \"absent\" }","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 not a directory; declare it under [bootstrap.files]\"));\n        }\n    }\n}","typeGuard":"fn directory_removal_is_safe(path: &std::path::Path) -> bool {\n    match std::fs::symlink_metadata(path) {\n        Err(_) => true,\n        Ok(m) => m.file_type().is_dir(),\n    }\n}","tryCatchPattern":null,"preventionTips":["Verify on-disk types before writing absent declarations","Keep a single source of truth for which paths are files vs directories"],"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"}