{"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":null,"httpStatus":null,"severity":"error","filePath":"src/oci/builder.rs","lineNumber":1110,"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":1092,"sourceCodeEnd":1128,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/oci/builder.rs#L1092-L1128","documentation":"When assembling dotfile layer entries, add_file detects that a path previously registered as a directory is now also being added as a file. An OCI layer cannot contain the same path as both a file and a directory, so the build fails with the conflicting path.","triggerScenarios":"build_dotfiles_layer adding entries where one [dotfiles] target's normalized OCI path equals another entry that was added as a directory — e.g. one entry targets /home/u/.config (a dir, via symlink-each or dir copy) and another targets /home/u/.config as a file.","commonSituations":"Two dotfiles entries whose targets differ only by trailing slash or normalization (\"~/.config\" vs \"/home/u/.config\"); one entry for a .config directory and another accidentally targeting the same path as a file.","solutions":["Rename one of the conflicting [dotfiles] targets so each OCI path is either a file or a directory, not both.","If one entry should live inside the other, nest the target, e.g. target \"/home/u/.config/git/config\" instead of \"/home/u/.config\".","Check for duplicate entries after normalization (absolutize ~, remove trailing slashes) and deduplicate the config."],"exampleFix":"// before — two entries normalize to /home/u/.config\n[dotfiles.\"~/.config\"]\nmode = \"symlink-each\"\nsource = \"/home/me/dotfiles\"\n[dotfiles.\"/home/u/.config\"]\nmode = \"content\"\ncontents = \"x\"\n\n// after — nest the file inside the dir\n[dotfiles.\"/home/u/.config/extra\"]\nmode = \"content\"\ncontents = \"x\"","handlingStrategy":"validation","validationCode":"# assert no two [dotfiles] targets normalize to the same OCI path\n# (absolutize ~, strip trailing '/'); fail on duplicates\ntargets=$(grep -oP '(?<=\\[dotfiles\\.\")[^\"]+' mise.toml | sort)\n[ \"$(echo \"$targets\" | sort -u | wc -l)\" = \"$(echo \"$targets\" | wc -l)\" ] || {\n  echo \"duplicate dotfiles targets\" >&2; exit 1;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep each OCI target path declared exactly once in [dotfiles].","Nest files under directory targets (target \"/home/u/.config/git/config\") rather than reusing the dir path.","Normalize (~ expansion, trailing slashes) mentally when writing targets."],"tags":["oci","dotfiles","conflict","layer"],"backgroundTag":"file-already-exists","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}