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

  1. Rename one of the conflicting [dotfiles] targets so each OCI path is either a file or a directory, not both.
  2. If one entry should live inside the other, nest the target, e.g. target "/home/u/.config/git/config" instead of "/home/u/.config".
  3. 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

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


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)