jdx/mise · error

destination variants require an explicit source unless the…

Error message

destination variants require an explicit source unless the entry key is a relative source path

What it means

This error comes from `logical_source_path` in the dotfiles system. When a `[dotfiles]` entry overrides destination targets via `variants` but omits an explicit `source`, the entry key itself is treated as the source path. That only works if the key is a relative source path; an absolute key (after `~/` expansion) cannot be reinterpreted as a source, so the library refuses it.

Solutions

  1. Add an explicit `source = "relative/path"` to the entry table
  2. Change the entry key to a relative source path (e.g. `"dotfiles/gitconfig" = { variants = [...] }`) so the source can be implied
  3. Remove the `variants` block if per-destination targets are not needed

Example fix

// before
"~/.zshrc" = { variants = [ { target = "~/.zshrc" }, { target = "~/.config/zsh/rc" } ] }
// after
"~/.zshrc" = { source = "dotfiles/zshrc", variants = [ { target = "~/.zshrc" }, { target = "~/.config/zsh/rc" } ] }
Defensive patterns

Strategy: validation

Validate before calling

function hasExplicitSource(entry) {
  return typeof entry === 'string' || (entry && typeof entry.source === 'string' && entry.source.length > 0);
}
// before relying on implied source: if (entry.variants && !hasExplicitSource(entry) && isAbsolute(entryKey)) throw ...

Type guard

const isRelativeSourceKey = (key: string) => !key.startsWith('/') && !key.startsWith('~') && !/^[A-Za-z]:[\\/]/.test(key);

Prevention

When it happens

Trigger: Declaring a dotfile entry whose key is an absolute path (e.g. `"~/.gitconfig"`) or `/absolute/path`, with `variants` that set per-destination targets, and no `source` field in the entry table.

Common situations: Users copy a dotfile entry that previously had `source = "dotfiles/gitconfig"`, delete the source field while adding a `variants` block to target multiple machines, or write the key as an absolute destination path assuming the source is inferred from somewhere else.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at src/system/files.rs:247

    } else {
        None
    };
    if variants.is_empty() && resolve_target_arg(target).is_relative() {
        bail!("target must be absolute or start with ~/");
    }
    for variant in variants {
        let destination = variant.target.as_deref().unwrap_or(target);
        if !variant_target_is_absolute(destination) {
            bail!("variant target must be absolute or start with ~/");
        }
    }
    Ok(implied_source)
}

fn logical_source_path(key: &str) -> Result<PathBuf> {
    let path = file::replace_path(key);
    if !path.is_relative() {
        bail!(
            "destination variants require an explicit source unless the entry key is a relative source path"
        );
    }
    let mut relative = PathBuf::new();
    for component in path.components() {
        match component {
            std::path::Component::Normal(component) => relative.push(component),
            std::path::Component::CurDir => {}
            std::path::Component::ParentDir => {
                bail!("an implied source entry key must not contain '..'");
            }
            std::path::Component::RootDir | std::path::Component::Prefix(_) => {
                bail!("an implied source entry key must be relative");
            }
        }
    }
    if relative.as_os_str().is_empty() {
        bail!("an implied source entry key must not be empty");

View on GitHub (pinned to 533346cc37)