jdx/mise · error
[dotfiles]: duplicate OCI path
Error message
[dotfiles]: duplicate OCI path {path:?} as both file and directory What it means
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.
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.
Example fix
// before — two entries normalize to /home/u/.config [dotfiles."~/.config"] mode = "symlink-each" source = "/home/me/dotfiles" [dotfiles."/home/u/.config"] mode = "content" contents = "x" // after — nest the file inside the dir [dotfiles."/home/u/.config/extra"] mode = "content" contents = "x"
Defensive patterns
Strategy: validation
Validate before calling
# assert no two [dotfiles] targets normalize to the same OCI path
# (absolutize ~, strip trailing '/'); fail on duplicates
targets=$(grep -oP '(?<=\[dotfiles\.")[^"]+' mise.toml | sort)
[ "$(echo "$targets" | sort -u | wc -l)" = "$(echo "$targets" | wc -l)" ] || {
echo "duplicate dotfiles targets" >&2; exit 1;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- [dotfiles]: duplicate OCI file path
- cannot copy a file or symlink to the image root
- changed after the operation; left untouched
- changed while preparing enrollment; concurrent declaration…
- choose only one resolution for
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/0288da38f2f29dde.
Report an issue: GitHub.
Appendix: source
Thrown at src/oci/builder.rs:1110
)?;
}
Ok(())
}
#[derive(Default)]
struct DotfilesLayerEntries {
files: IndexMap<String, (Vec<u8>, u32)>,
dirs: IndexSet<String>,
}
type DotfilesLayerFile = (String, Vec<u8>, u32);
type DotfilesLayerFiles = Vec<DotfilesLayerFile>;
type DotfilesLayerDirs = Vec<String>;
impl DotfilesLayerEntries {
fn add_file(&mut self, path: String, contents: Vec<u8>, mode: u32) -> Result<()> {
if self.dirs.contains(&path) {
bail!("[dotfiles]: duplicate OCI path {path:?} as both file and directory");
}
if let Some((existing_contents, existing_mode)) = self.files.get(&path) {
if existing_contents != &contents || *existing_mode != mode {
bail!("[dotfiles]: duplicate OCI file path {path:?}");
}
return Ok(());
}
self.files.insert(path, (contents, mode));
Ok(())
}
fn add_dir(&mut self, path: String) -> Result<()> {
if self.files.contains_key(&path) {
bail!("[dotfiles]: duplicate OCI path {path:?} as both file and directory");
}
self.dirs.insert(path);
Ok(())
}View on GitHub (pinned to 533346cc37)