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
- Inspect the chain in the error message to find the loop, then make at least one alias point to a real (non-alias) command.
- If you intended command chaining, note Cargo aliases expand to a single subcommand, not pipelines; use a shell wrapper or external cargo-<ext> instead.
- 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 renaming a subcommand, update every alias that pointed at the old name.
- Review alias definitions after refactors; cycles are usually copy-paste mistakes.
- Prefer aliases that resolve to real builtins, not to other aliases.
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
- subcommand is required, add a subcommand to the command alia
- subcommand is required, but `{alias_name}` is empty
- argument for --color must be auto, always, or never, but fou
- invalid character `+` in dependency name: `+{toolchain}`
- feature `{feature}` must be qualified by the dependency it's
AI-assisted analysis of rust-lang/cargo@0e07a15537 (2026-08-06).
Data as JSON: /data/errors/6f499f304a720314.json.
Report an issue: GitHub.