jdx/mise · error

an implied source entry key must be relative

Error message

an implied source entry key must be relative

What it means

When a dotfile entry key is used as an implied source (variants with no explicit source), the key must resolve to a relative path. Keys starting with `/`, a Windows drive/UNC prefix, or `~/` become absolute after `replace_path` expansion, which cannot serve as a relative source, so this bail fires.

Solutions

  1. Add an explicit `source` field to the entry
  2. Use a relative key like `"dotfiles/gitconfig"` so it can be the implied source
  3. Drop the `variants` block if a single destination is sufficient

Example fix

// before
"~/.gitconfig" = { variants = [ { target = "~/.gitconfig" } ] }
// after
"~/.gitconfig" = { source = "gitconfig", variants = [ { target = "~/.gitconfig" } ] }
Defensive patterns

Strategy: validation

Validate before calling

function keyIsRelativeAfterExpand(key) {
  const p = key.startsWith('~') ? os.homedir() + key.slice(1) : key;
  return !path.isAbsolute(p);
}

Type guard

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

Prevention

When it happens

Trigger: Entry key is absolute like `"/etc/foo"`, `"C:/Users/x/file"`, or expands via `~` to an absolute home path, while the entry uses `variants` without a `source` field.

Common situations: Writing the destination (e.g. `~/.gitconfig`) as the key and expecting the source to be inferred; this only works for keys that are relative paths into the dotfiles directory.

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/fad6fc7cd218b3d5. Report an issue: GitHub.

Appendix: source

Thrown at src/system/files.rs:260

}

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");
    }
    Ok(relative)
}

/// Inactive variants can contain another platform's absolute path syntax.
/// The selected destination is still checked with native path rules before use.
fn variant_target_is_absolute(target: &str) -> bool {
    let bytes = target.as_bytes();
    resolve_target_arg(target).is_absolute()
        || target.starts_with('/')
        || target.starts_with("\\\\")
        || (bytes.len() >= 3
            && bytes[0].is_ascii_alphabetic()

View on GitHub (pinned to 533346cc37)