rust-lang/cargo · error

alias {} has unresolvable recursive definition: {} -> {}

Error message

alias {} has unresolvable recursive definition: {} -> {}

What it means

From expand_aliases (src/bin/cargo/cli.rs:383-393). It maintains already_expanded, a list of alias names visited. After resolving alias A it pushes A, then if the next resolved command new_cmd is already in the list, it bails printing the full A -> B -> ... -> new_cmd chain. This breaks corecursive or self-referential alias definitions that would otherwise recurse forever.

Source

Thrown at src/bin/cargo/cli.rs:386

                );
                // new_args strips out everything before the subcommand, so
                // capture those global options now.
                // Note that an alias to an external command will not receive
                // these arguments. That may be confusing, but such is life.
                let global_args = GlobalArgs::new(sub_args);
                let new_args = cli(gctx).no_binary_name(true).try_get_matches_from(alias)?;

                let Some(new_cmd) = new_args.subcommand_name() else {
                    return Err(anyhow!(
                        "subcommand is required, add a subcommand to the command alias `alias.{cmd}`"
                    )
                        .into());
                };

                already_expanded.push(cmd.to_string());
                if already_expanded.contains(&new_cmd.to_string()) {
                    // Crash if the aliases are corecursive / unresolvable
                    return Err(anyhow!(
                        "alias {} has unresolvable recursive definition: {} -> {}",
                        already_expanded[0],
                        already_expanded.join(" -> "),
                        new_cmd,
                    )
                    .into());
                }

                let (expanded_args, _) = expand_aliases(gctx, new_args, already_expanded)?;
                return Ok((expanded_args, global_args));
            }
            (None, Err(e)) => return Err(e.into()),
        }
    };

    Ok((args, GlobalArgs::default()))
}

View on GitHub (pinned to 0e07a15537)

Solutions

  1. Inspect the chain in the error message to find the loop, then make at least one alias point to a real (non-alias) command.
  2. If you intended command chaining, note Cargo aliases expand to a single subcommand, not pipelines; use a shell wrapper or external cargo-<ext> instead.
  3. Remove the alias that closes the cycle.

Example fix

# before (.cargo/config.toml) — a and b recurse
[alias]
a = "b"
b = "a"

# after
[alias]
a = "build"
b = "test"
Defensive patterns

Strategy: validation

Validate before calling

// Detect cycles in alias definitions before Cargo expands them
use std::collections::{BTreeMap, HashSet};

fn alias_has_cycle(aliases: &BTreeMap<String, String>) -> Option<String> {
    for start in aliases.keys() {
        let mut seen = HashSet::new();
        let mut cur = Some(start.clone());
        while let Some(c) = cur {
            if !seen.insert(c.clone()) {
                return Some(start.clone());
            }
            // take first token of the alias definition
            cur = aliases.get(&c)
                .and_then(|d| d.split_whitespace().next().map(str::to_owned))
                .filter(|n| aliases.contains_key(n));
        }
    }
    None
}

Prevention

When it happens

Trigger: `alias.a = "b"` combined with `alias.b = "a"` (mutual recursion), or `alias.a = "a"` (self recursion), or a longer cycle a->b->c->a.

Common situations: Refactoring aliases and accidentally creating a cycle; copy-paste where two aliases point at each other; renaming a command but forgetting to update its alias target.

Related errors


AI-assisted analysis of rust-lang/cargo@0e07a15537 (2026-08-06). Data as JSON: /data/errors/6f499f304a720314.json. Report an issue: GitHub.