{"record":{"id":"cfacdbf831d24722","repo":"jdx/mise","slug":"variants-allow-only-one-default-without-os-or-pro","errorCode":null,"errorMessage":"variants allow only one default, without os or profile selectors","messagePattern":"variants allow only one default, without os or profile selectors","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/select.rs","lineNumber":92,"sourceCode":"#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) enum Selection {\n    /// The entry has no variants: one stream for every machine.\n    Single,\n    /// The variant this machine uses.\n    Variant(Variant),\n    /// No variant matches and none is the default: do not capture or apply.\n    NoMatch,\n    /// Two variants match with the same specificity.\n    Ambiguous(Vec<Variant>),\n}\n\n/// Reject fallbacks whose selectors would be ignored during selection.\npub(crate) fn validate(variants: &[Variant]) -> eyre::Result<()> {\n    let mut default_seen = false;\n    for variant in variants {\n        if variant.default {\n            if default_seen || !variant.os.is_empty() || variant.profile.is_some() {\n                eyre::bail!(\"variants allow only one default, without os or profile selectors\");\n            }\n            default_seen = true;\n        }\n    }\n    Ok(())\n}\n\n/// Picks the variant for this machine given the active mise environments.\npub(crate) fn select(variants: &[Variant], environments: &[String]) -> Selection {\n    if variants.is_empty() {\n        return Selection::Single;\n    }\n    let matching: Vec<&Variant> = variants\n        .iter()\n        .filter(|variant| !variant.default && variant.matches(environments))\n        .collect();\n    let best = matching.iter().map(|variant| variant.specificity()).max();\n    match best {","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/select.rs#L74-L110","documentation":"validate rejects variant fallback lists where selector-bearing rules would be silently shadowed: there must be at most one default variant, and the default must not carry an os or profile selector, because defaults are only consulted when nothing matches and such selectors would never be honored.","triggerScenarios":"Declaring two variants with default=true, or a single default variant with a non-empty os field or a Some(profile), in the variant list passed to validate.","commonSituations":"Copy-pasting a variant row and forgetting to clear default: true; adding os/profile to a fallback entry intending it to be conditional when it must be a plain default; merging config files that each defined a default.","solutions":["Keep exactly one default variant with empty os and no profile","Turn selector-bearing entries into non-default variants with explicit os/profile selectors","Remove default: true from duplicates so only one fallback remains"],"exampleFix":"// before\nvariants = [\n  { default = true, os = \"linux\" },\n  { default = true }\n]\n// after\nvariants = [\n  { os = \"linux\", version = \"...\" },\n  { default = true }\n]","handlingStrategy":"validation","validationCode":"let defaults: Vec<_> = variants.iter().filter(|v| v.default).collect();\nassert!(defaults.len() <= 1, \"at most one default variant\");\nif let Some(d) = defaults.first() {\n    assert!(d.os.is_empty() && d.profile.is_none(), \"default must be unselected\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only mark plain fallback entries as default","Give every os/profile-specific variant an explicit selector and default=false","Lint variant tables after merging config files"],"tags":["validation","variants","config","selector"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}