nikivdev/code · error

ambiguous recipe selector

Error message

ambiguous recipe selector

What it means

When no recipe id equals the selector, `select_recipe` falls back to case-insensitive exact-name matching; if more than one recipe shares that name, it prints the candidate list to stderr (via `ambiguous_selector_error`) and bails. The library never guesses between equal-priority matches.

Source

Thrown at src/recipe.rs:637

    if normalized.is_empty() {
        bail!("empty recipe selector")
    }

    if let Some(recipe) = recipes.iter().find(|r| r.id == normalized) {
        return Ok(recipe);
    }

    let lowered = normalized.to_ascii_lowercase();
    let exact_name: Vec<&Recipe> = recipes
        .iter()
        .filter(|r| r.name.to_ascii_lowercase() == lowered)
        .collect();
    if exact_name.len() == 1 {
        return Ok(exact_name[0]);
    }
    if exact_name.len() > 1 {
        ambiguous_selector_error(selector, &exact_name)?;
        bail!("ambiguous recipe selector")
    }

    let contains: Vec<&Recipe> = recipes
        .iter()
        .filter(|r| {
            r.id.to_ascii_lowercase().contains(&lowered)
                || r.name.to_ascii_lowercase().contains(&lowered)
        })
        .collect();
    if contains.len() == 1 {
        return Ok(contains[0]);
    }
    if contains.is_empty() {
        bail!("no recipe matched '{}'", selector);
    }
    ambiguous_selector_error(selector, &contains)?;
    bail!("ambiguous recipe selector")
}

View on GitHub (pinned to a747e741ae)

Solutions

  1. Use the unique recipe id instead of the display name (ids are listed on stderr and by the list command)
  2. Rename one of the colliding recipes so names are unique
  3. Restrict the scope (global vs project) so only one matching recipe is loaded
  4. Deduplicate duplicated recipe files in the recipe directories

Example fix

// before: two recipes named "Build"
$ f recipe run build
recipe selector 'build' matched multiple recipes:
  - build.global (Build)
  - build.web (Build)
ambiguous recipe selector
// after
$ f recipe run build.web   # select by unique id
Defensive patterns

Strategy: validation

Validate before calling

// preflight: ensure the name resolves to exactly one recipe before selecting
fn unique_name<'a>(recipes: &'a [Recipe], name: &str) -> Option<&'a Recipe> {
    let lowered = name.to_ascii_lowercase();
    let m: Vec<&Recipe> = recipes.iter().filter(|r| r.name.to_ascii_lowercase() == lowered).collect();
    if m.len() == 1 { Some(m[0]) } else { None } // None => select by id instead
}

Try / catch

match run_recipe(opts) {
    Err(e) if e.to_string().contains("ambiguous recipe selector") => {
        eprintln!("name matched multiple recipes; re-run with a unique recipe id (see stderr list)");
        std::process::exit(2);
    }
    other => other,
}

Prevention

When it happens

Trigger: Selecting by display name (not id) when two or more recipes in the loaded scope have the same (case-insensitively equal) name — e.g. two scopes each defining a recipe named 'build'.

Common situations: Duplicate recipe names across global and project recipe directories; copied recipe files where the name field wasn't changed; team recipes imported under the same name as a personal recipe.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/66a5e3990a5fee3a. Report an issue: GitHub.