jdx/mise · error · eyre::Report

[dotfiles]: duplicate OCI file path {path:?}

Error message

[dotfiles]: duplicate OCI file path {path:?}

What it means

Thrown while assembling the [dotfiles] OCI layer when two different sources resolve to the same image path with different bytes or modes. add_file is idempotent for identical (contents, mode) pairs, so this fires only on a genuine conflict, not on harmless overlap. It protects the one-path-one-file invariant of the layer before the tar is built.

Source

Thrown at src/oci/builder.rs:990

#[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(())
    }

    fn into_layer_inputs(self) -> (DotfilesLayerFiles, DotfilesLayerDirs) {
        let files = self
            .files

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Search mise.toml for [dotfiles] entries whose normalized targets (~/x -> root/x, /x -> x) are equal; give each a unique target path.
  2. If the duplicate is intentional (same file referenced twice), make the source files byte-identical and their modes equal, then add_file dedupes silently.
  3. Remove the redundant entry entirely and keep one canonical source for that destination path.

Example fix

# before
[dotfiles."~/.gitconfig-work"]
target = "~/.gitconfig"
[dotfiles."~/.gitconfig-home"]
target = "/root/.gitconfig"  # same image path, different contents

# after
[dotfiles."~/.gitconfig-work"]
target = "~/.gitconfig"
[dotfiles."~/.gitconfig-home"]
target = "~/.gitconfig-home"
Defensive patterns

Strategy: validation

Validate before calling

# before building, assert no two [dotfiles] targets normalize to the same path
import re, sys
entries = {"~/.gitconfig": "/root/.gitconfig", "~/b": "~/a"}  # parsed from mise.toml
def norm(t):
    if t == "~": return "root"
    if t.startswith("~/"): return "root/" + t[2:]
    return t.lstrip("/").replace("\\", "/")
seen = {}
for src, tgt in entries.items():
    n = norm(tgt)
    if n in seen and seen[n] != src:
        sys.exit(f"duplicate target {n!r} from {seen[n]} and {src}")
    seen[n] = src

Prevention

When it happens

Trigger: Two [dotfiles] entries whose normalized targets collide, e.g. [dotfiles."~/a"] target = "/root/a" and [dotfiles."~/b"] target = "~/a"; or a directory-source walk producing a file at a path another single-file entry already claimed with different contents or a different permission mode.

Common situations: Mapping several dotfiles into the same destination (all dumping into ~/), copying the same file twice via different home spellings (~/.gitconfig vs /root/.gitconfig) after editing one source but not the other, or overlapping directory sources where a file changed between walks.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/c2a73e821f56d1ca. Report an issue: GitHub.