{"record":{"id":"87f62b4765280189","repo":"jdx/mise","slug":"managed-system-path-is-declared-as-both-a-fil","errorCode":null,"errorMessage":"managed system path '{}' is declared as both a file and a directory","messagePattern":"managed system path '(.+?)' is declared as both a file and a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/managed_files.rs","lineNumber":209,"sourceCode":") -> Result<(\n    Vec<ManagedFileRequest>,\n    Vec<ManagedDirectoryRequest>,\n    Vec<ResourcePlan>,\n)> {\n    let mut files = vec![];\n    let mut unavailable = vec![];\n    let mut directories = directories_from_config(config)?;\n    let directory_states = directories\n        .iter()\n        .map(|directory| (directory.path.as_path(), directory.state))\n        .collect::<std::collections::HashMap<_, _>>();\n    for (path, (file, base)) in merged_files_from_config(config)? {\n        let state = file.state;\n        match ManagedFileRequest::from_toml(config, path.clone(), file, &base, secrets) {\n            Ok(file) => files.push(file),\n            Err(error) if super::secrets::is_unavailable(&error) => {\n                if directory_states.contains_key(path.as_path()) {\n                    bail!(\n                        \"managed system path '{}' is declared as both a file and a directory\",\n                        path.display()\n                    );\n                }\n                validate_present_ancestors(&path, state, &directory_states)?;\n                unavailable.push(ResourcePlan::new(\n                    ResourceId::new(\"file\", path.to_string_lossy().into_owned()),\n                    \"not inspected: required secret unavailable\",\n                    \"template rendered\",\n                    ResourceAction::Unknown,\n                ));\n            }\n            Err(error) => return Err(error),\n        }\n    }\n    ignore_non_linux_account_principals(config, &mut files, &mut directories);\n    validate_requests(&files, &directories)?;\n    inspect_paths(&mut files, &mut directories)?;","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/managed_files.rs#L191-L227","documentation":"During planning of `[bootstrap.files]` / `[bootstrap.directories]`, a file entry's construction failed with a secret-unavailable error (its template references a secret whose env var/keychain value is missing, detected via secrets::is_unavailable downcasting to SecretUnavailable), AND the same normalized path is also declared under `[bootstrap.directories]`. Because the file couldn't be inspected, mise can't reconcile the file/directory overlap safely, so it bails instead of guessing (managed_files.rs:207-213).","triggerScenarios":"A mise.toml with both `[bootstrap.files.\"/etc/app\"]` (templated, whose required secret env var is unset) and `[bootstrap.directories.\"/etc/app\"]`. Normally the duplicate-path check at validate_requests (line 394) catches overlaps, but the secret-unavailable branch runs earlier during request construction, so it has its own copy of the conflict check before planning a 'not inspected' placeholder.","commonSituations":"Refactoring config and moving a path from files to directories (or vice versa) while the old block is left behind; templated file whose secret wasn't provisioned on a new machine, exposing the latent duplicate declaration; layered config files (project + ~/.config/mise) each declaring the same path in different tables.","solutions":["Remove one of the two declarations for the same path — a path must be either a file or a directory, never both","If you intended to migrate, delete the old `[bootstrap.files.\"<path>\"]` block entirely and keep the directory entry (or the reverse)","Provision the missing secret (set the env var / keychain entry) so the file request constructs normally — though the duplicate will then be caught by validate_requests anyway, so the declaration conflict must be fixed regardless","Check every layered mise.toml (project, ~/.config/mise, MISE_CONFIG_FILE) for a stale duplicate"],"exampleFix":"# before\n[bootstrap.files.\"/etc/myapp\"]\ncontent = \"{{ secrets.token }}\"\ntemplate = true\n\n[bootstrap.directories.\"/etc/myapp\"]\nowner = \"root\"\n\n# after\n[bootstrap.directories.\"/etc/myapp\"]\nowner = \"root\"","handlingStrategy":"validation","validationCode":"# Validate before running bootstrap: no path in both tables (any layer), and secrets set for templated files\nuse std::collections::HashSet;\nlet dirs: HashSet<_> = layers().iter().flat_map(|c| c.bootstrap.directories.keys().map(normalize)).collect();\nfor c in layers() {\n    for path in c.bootstrap.files.keys() {\n        assert!(!dirs.contains(&normalize(path)), \"path {path} declared as both file and directory\");\n    }\n}\nfor f in templated_files() { ensure_secret_available(f)?; } // fail loudly before planning","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat a path as having exactly one type across ALL config layers — enforce with a lint script in CI","When migrating a path between files and directories, delete the old block in the same commit","Provide required secret env vars (or keychain entries) before running bootstrap so the unavailable-secret branch is never taken"],"tags":["bootstrap","config-validation","duplicate-declaration","secrets","managed-files"],"backgroundTag":"duplicate-config-declaration","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}