{"record":{"id":"0288da38f2f29dde","repo":"jdx/mise","slug":"dotfiles-duplicate-oci-path-path-as-both-fi","errorCode":null,"errorMessage":"[dotfiles]: duplicate OCI path {path:?} as both file and directory","messagePattern":"\\[dotfiles\\]: duplicate OCI path (.+?) as both file and directory","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/oci/builder.rs","lineNumber":986,"sourceCode":"        )?;\n    }\n    Ok(())\n}\n\n#[derive(Default)]\nstruct DotfilesLayerEntries {\n    files: IndexMap<String, (Vec<u8>, u32)>,\n    dirs: IndexSet<String>,\n}\n\ntype DotfilesLayerFile = (String, Vec<u8>, u32);\ntype DotfilesLayerFiles = Vec<DotfilesLayerFile>;\ntype DotfilesLayerDirs = Vec<String>;\n\nimpl DotfilesLayerEntries {\n    fn add_file(&mut self, path: String, contents: Vec<u8>, mode: u32) -> Result<()> {\n        if self.dirs.contains(&path) {\n            bail!(\"[dotfiles]: duplicate OCI path {path:?} as both file and directory\");\n        }\n        if let Some((existing_contents, existing_mode)) = self.files.get(&path) {\n            if existing_contents != &contents || *existing_mode != mode {\n                bail!(\"[dotfiles]: duplicate OCI file path {path:?}\");\n            }\n            return Ok(());\n        }\n        self.files.insert(path, (contents, mode));\n        Ok(())\n    }\n\n    fn add_dir(&mut self, path: String) -> Result<()> {\n        if self.files.contains_key(&path) {\n            bail!(\"[dotfiles]: duplicate OCI path {path:?} as both file and directory\");\n        }\n        self.dirs.insert(path);\n        Ok(())\n    }","sourceCodeStart":968,"sourceCodeEnd":1004,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/oci/builder.rs#L968-L1004","documentation":"While assembling the dotfiles layer, mise tracks paths as either files or directories (DotfilesLayerEntries). A path previously registered as a directory cannot later be registered as a file — this error fires when the exact same OCI path is added both ways, e.g. one entry contributes a directory at path X and another entry contributes a file at path X. Building a tar layer with a path as both types would be ambiguous and invalid.","triggerScenarios":"Two `[oci.dotfiles]` entries whose normalized targets collide: e.g. a symlink-each/copy entry creating directory `~/config` while another entry writes file `~/config`; or a directory-valued entry (its dir path registered via add_dir) colliding with a file entry of the same name.","commonSituations":"Overlapping dotfile globs or targets like `\"~/.config\" = {source = \"./config\", mode = \"copy\"}` and `\"~/.config\" = {mode = \"content\", content = \"...\"}`; restructuring dotfiles so a former file target becomes a directory target in a stale entry.","solutions":["Find the two entries whose target paths normalize to the same value and rename one of them","If a directory and file genuinely share a name, move the file under the directory (e.g. `~/.config/x` vs `~/.config`)","Remove the stale entry left over from restructuring your dotfiles config"],"exampleFix":"# before (mise.toml)\n[oci.dotfiles]\n\"~/.config\" = { source = \"./config\", mode = \"copy\" }       # directory\n\"~/.config\" = { mode = \"content\", content = \"key=1\\n\" }  # file — collision\n\n# after (mise.toml)\n[oci.dotfiles]\n\"~/.config\" = { source = \"./config\", mode = \"copy\" }\n\"~/.config/override.conf\" = { mode = \"content\", content = \"key=1\\n\" }","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport tomllib, sys\ncfg = tomllib.load(open('mise.toml', 'rb'))\npaths = {}\nfor target, spec in (cfg.get('oci', {}).get('dotfiles', {}) or {}).items():\n    norm = '/' + target.strip('/').replace('~', 'root', 1) if target.startswith('~') else target\n    kind = 'file' if isinstance(spec, dict) and spec.get('mode') == 'content' else 'dir-or-file'\n    if paths.setdefault(norm, kind) != kind:\n        sys.exit(f'duplicate target {norm!r} with conflicting kinds')\nprint('dotfile targets OK')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Give each dotfiles entry a unique normalized target path","When a directory target exists, place file targets under it (dir/file), never at the same path","Review dotfiles config after restructuring so stale entries cannot collide with new ones"],"tags":["oci","dotfiles","build","path-collision","config"],"backgroundTag":"path-conflict-in-image-build","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}