{"record":{"id":"fad6fc7cd218b3d5","repo":"jdx/mise","slug":"an-implied-source-entry-key-must-be-relative","errorCode":null,"errorMessage":"an implied source entry key must be relative","messagePattern":"an implied source entry key must be relative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/files.rs","lineNumber":260,"sourceCode":"}\n\nfn logical_source_path(key: &str) -> Result<PathBuf> {\n    let path = file::replace_path(key);\n    if !path.is_relative() {\n        bail!(\n            \"destination variants require an explicit source unless the entry key is a relative source path\"\n        );\n    }\n    let mut relative = PathBuf::new();\n    for component in path.components() {\n        match component {\n            std::path::Component::Normal(component) => relative.push(component),\n            std::path::Component::CurDir => {}\n            std::path::Component::ParentDir => {\n                bail!(\"an implied source entry key must not contain '..'\");\n            }\n            std::path::Component::RootDir | std::path::Component::Prefix(_) => {\n                bail!(\"an implied source entry key must be relative\");\n            }\n        }\n    }\n    if relative.as_os_str().is_empty() {\n        bail!(\"an implied source entry key must not be empty\");\n    }\n    Ok(relative)\n}\n\n/// Inactive variants can contain another platform's absolute path syntax.\n/// The selected destination is still checked with native path rules before use.\nfn variant_target_is_absolute(target: &str) -> bool {\n    let bytes = target.as_bytes();\n    resolve_target_arg(target).is_absolute()\n        || target.starts_with('/')\n        || target.starts_with(\"\\\\\\\\\")\n        || (bytes.len() >= 3\n            && bytes[0].is_ascii_alphabetic()","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/files.rs#L242-L278","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add an explicit `source` field to the entry","Use a relative key like `\"dotfiles/gitconfig\"` so it can be the implied source","Drop the `variants` block if a single destination is sufficient"],"exampleFix":"// before\n\"~/.gitconfig\" = { variants = [ { target = \"~/.gitconfig\" } ] }\n// after\n\"~/.gitconfig\" = { source = \"gitconfig\", variants = [ { target = \"~/.gitconfig\" } ] }","handlingStrategy":"validation","validationCode":"function keyIsRelativeAfterExpand(key) {\n  const p = key.startsWith('~') ? os.homedir() + key.slice(1) : key;\n  return !path.isAbsolute(p);\n}","typeGuard":"const isPlainRelativeKey = (key: string) => !key.startsWith('/') && !key.startsWith('~') && !/^[A-Za-z]:[\\\\/]/.test(key) && !key.startsWith('\\\\\\\\');","tryCatchPattern":null,"preventionTips":["Use relative keys when implying sources from the key","Pair absolute/`~` keys with an explicit `source`"],"tags":["config","dotfiles","path-validation"],"backgroundTag":"invalid-config-value","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}