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
- Remove or rename one of the conflicting declarations so each target is declared once
- Set both conflicting entries to `mode = "track"` or both to `mode = "symlink_each"` if that matches intent (these combinations are allowed)
- 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
- Keep one declaration per destination across all config layers
- When layering team + personal dotfile configs, audit for overlapping targets
- Use `mode = "track"` (or `symlink_each` on both) only when shared ownership is intentional
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
- an implied source entry key must be relative
- an implied source entry key must not be empty
- an implied source entry key must not contain '..'
- bootstrap service ' ' cannot be both masked and running
- bootstrap service ' ' cannot be both masked and enabled
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)