{"record":{"id":"e6a887e5c3121117","repo":"jdx/mise","slug":"refusing-to-remove-directory-as-a-file-declare","errorCode":null,"errorMessage":"refusing to remove directory {} as a file; declare it in [bootstrap.directories]","messagePattern":"refusing to remove directory (.+?) as a file; declare it in \\[bootstrap\\.directories\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/managed_files.rs","lineNumber":596,"sourceCode":"            owner,\n            group,\n            mode,\n            state: config.state,\n            replace: config.replace,\n            notify: config.notify,\n            origin,\n            inspection: None,\n        })\n    }\n\n    pub(crate) fn plan(&self) -> Result<ResourcePlan> {\n        plan_file(self).map(|plan| plan.with_origin(self.origin.clone()))\n    }\n\n    fn operation(&self) -> Result<Option<PrivilegedAction>> {\n        match self.plan()?.action {\n            ResourceAction::Noop => return Ok(None),\n            ResourceAction::Unknown if self.state == ManagedState::Absent => bail!(\n                \"refusing to remove directory {} as a file; declare it in [bootstrap.directories]\",\n                self.path.display()\n            ),\n            ResourceAction::Unknown => bail!(\n                \"refusing to replace non-file path {}; set replace = true to allow replacement\",\n                self.path.display()\n            ),\n            _ => {}\n        }\n        Ok(Some(match self.state {\n            ManagedState::Present => PrivilegedAction::WriteFile {\n                path: self.path.clone(),\n                content: self.content.clone().expect(\"present file has content\"),\n                owner: self.owner.clone(),\n                group: self.group.clone(),\n                mode: self.mode,\n                replace: self.replace,\n            },","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/managed_files.rs#L578-L614","documentation":"Raised in ManagedFileRequest::operation when a file entry with state=\"absent\" is planned and on-disk inspection returns ResourceAction::Unknown because the target path is actually a directory. The library refuses to recursively delete a directory via the file resource; the user must declare it under [bootstrap.directories] instead.","triggerScenarios":"Converging a [bootstrap.files] entry with state = \"absent\" whose path exists on disk as a directory (plan_file cannot classify it as a regular file), producing the (ResourceAction::Unknown, Absent) arm.","commonSituations":"A directory was created at a path previously occupied by a file (app version change); the config author assumed the path was a file; migrating config where an old symlink target became a directory; tests running on hosts with different layouts.","solutions":["Move the entry from [bootstrap.files] to [bootstrap.directories] with state = \"absent\" so it is removed as a directory.","Or remove the directory manually first, then run bootstrap again.","If the directory should stay, drop the entry or change state to \"present\" under [bootstrap.directories]."],"exampleFix":"# before\n[[bootstrap.files]]\npath = \"/var/lib/myapp/old-plugin\"\nstate = \"absent\"\n\n# after\n[[bootstrap.directories]]\npath = \"/var/lib/myapp/old-plugin\"\nstate = \"absent\"\nrecursive = true","handlingStrategy":"validation","validationCode":"// before running bootstrap, check declared-absent file paths on the host\nfor path in &declared_absent_files {\n    if path.is_dir() {\n        eprintln!(\"{} is a directory; declare under [bootstrap.directories]\", path.display());\n    }\n}","typeGuard":"fn is_directory(path: &std::path::Path) -> bool {\n    std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Before declaring a path absent as a file, stat it on target hosts to confirm it is a regular file.","Keep directory-type resources under [bootstrap.directories] and file types under [bootstrap.files].","After app upgrades that change file/dir layouts, audit bootstrap entries for type drift."],"tags":["filesystem","bootstrap","safety","path-type-mismatch"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}