jdx/mise · error

conflicting dotfile declarations for

Error message

conflicting dotfile declarations for {}

  first:
    {}

  second:
    {}

What it means

`composed_files_from_config` detects two `[dotfiles]` declarations that would manage the same destination target. If both are in owning (non-Track) modes and they are not both `symlink_each`, the library cannot decide which declaration owns the destination and bails with a message describing both origins.

Solutions

  1. Remove or rename one of the conflicting declarations so each target is declared once
  2. Set both conflicting entries to `mode = "track"` or both to `mode = "symlink_each"` if that matches intent (these combinations are allowed)
  3. Move one declaration to a different destination path

Example fix

// before
"~/.gitconfig" = "dotfiles/gitconfig"
"~/.gitconfig" = { source = "work/gitconfig" }
// after
"~/.gitconfig" = "dotfiles/gitconfig"   # single owner
Defensive patterns

Strategy: validation

Validate before calling

function findDuplicateTargets(entries) {
  const seen = new Map(); const dupes = [];
  for (const [target, e] of Object.entries(entries)) {
    if (seen.has(target)) dupes.push(target); else seen.set(target, e);
  }
  return dupes;
}

Try / catch

try { applyFiles(config); } catch (e) {
  if (String(e.message).startsWith('conflicting dotfile declarations for')) {
    // parse target from message, dedupe declarations for that target
  } else throw e;
}

Prevention

When it happens

Trigger: Two entries in `[dotfiles]` (possibly across layered config files) resolve to the same absolute destination, e.g. `"~/.gitconfig" = "a/gitconfig"` and `"~/.gitconfig" = { source = "b/gitconfig" }`, where neither is `mode = "track"` and not both are `symlink_each`.

Common situations: Merging dotfile configs from team and personal files that both ship the same rc file; refactoring keys and leaving a duplicate behind; an included config re-declaring an entry already present globally.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/25853c8bda4a2d4b. Report an issue: GitHub.

Appendix: source

Thrown at src/system/files.rs:455

            if let Some(existing) = siblings
                .iter_mut()
                .find(|existing| file_requests_match(config, existing, &request))
            {
                // the same declaration from a later layer: what it says
                // about `enabled`, the policies, and the variants wins, so a
                // local `enabled = false` overrides what
                // it inherited instead of being dropped as a duplicate; what
                // it leaves unsaid stays inherited
                existing.override_from(request);
                continue;
            }
            if let Some(existing) = siblings.iter().find(|existing| {
                existing.mode != FileMode::Track
                    && request.mode != FileMode::Track
                    && (existing.mode != FileMode::SymlinkEach
                        || request.mode != FileMode::SymlinkEach)
            }) {
                bail!(
                    "conflicting dotfile declarations for {}\n\n  first:\n    {}\n\n  second:\n    {}",
                    request.target.display(),
                    existing.origin.conflict_description(),
                    request.origin.conflict_description(),
                );
            }
            siblings.push(request);
        }
    }
    Ok(composed.into_values().flatten().collect())
}

/// Whether a declaration comes from the system or global layers (or a root
/// they compose): the only layers history enrolls files from.
pub(crate) fn declaration_is_global(config: &Config, req: &FileRequest) -> bool {
    track_layer_allowed(&req.origin, &global_composed_roots(config))
}

View on GitHub (pinned to 533346cc37)