{"id":"6f499f304a720314","repo":"rust-lang/cargo","slug":"alias-has-unresolvable-recursive-definition","errorCode":null,"errorMessage":"alias {} has unresolvable recursive definition: {} -> {}","messagePattern":"alias (.+?) has unresolvable recursive definition: (.+?) -> (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/bin/cargo/cli.rs","lineNumber":386,"sourceCode":"                );\n                // new_args strips out everything before the subcommand, so\n                // capture those global options now.\n                // Note that an alias to an external command will not receive\n                // these arguments. That may be confusing, but such is life.\n                let global_args = GlobalArgs::new(sub_args);\n                let new_args = cli(gctx).no_binary_name(true).try_get_matches_from(alias)?;\n\n                let Some(new_cmd) = new_args.subcommand_name() else {\n                    return Err(anyhow!(\n                        \"subcommand is required, add a subcommand to the command alias `alias.{cmd}`\"\n                    )\n                        .into());\n                };\n\n                already_expanded.push(cmd.to_string());\n                if already_expanded.contains(&new_cmd.to_string()) {\n                    // Crash if the aliases are corecursive / unresolvable\n                    return Err(anyhow!(\n                        \"alias {} has unresolvable recursive definition: {} -> {}\",\n                        already_expanded[0],\n                        already_expanded.join(\" -> \"),\n                        new_cmd,\n                    )\n                    .into());\n                }\n\n                let (expanded_args, _) = expand_aliases(gctx, new_args, already_expanded)?;\n                return Ok((expanded_args, global_args));\n            }\n            (None, Err(e)) => return Err(e.into()),\n        }\n    };\n\n    Ok((args, GlobalArgs::default()))\n}\n","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/rust-lang/cargo/blob/0e07a155371a6ce88ae53a2c00df940280c09a67/src/bin/cargo/cli.rs#L368-L404","documentation":"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.","triggerScenarios":"`alias.a = \"b\"` combined with `alias.b = \"a\"` (mutual recursion), or `alias.a = \"a\"` (self recursion), or a longer cycle a->b->c->a.","commonSituations":"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.","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."],"exampleFix":"# before (.cargo/config.toml) — a and b recurse\n[alias]\na = \"b\"\nb = \"a\"\n\n# after\n[alias]\na = \"build\"\nb = \"test\"","handlingStrategy":"validation","validationCode":"// Detect cycles in alias definitions before Cargo expands them\nuse std::collections::{BTreeMap, HashSet};\n\nfn alias_has_cycle(aliases: &BTreeMap<String, String>) -> Option<String> {\n    for start in aliases.keys() {\n        let mut seen = HashSet::new();\n        let mut cur = Some(start.clone());\n        while let Some(c) = cur {\n            if !seen.insert(c.clone()) {\n                return Some(start.clone());\n            }\n            // take first token of the alias definition\n            cur = aliases.get(&c)\n                .and_then(|d| d.split_whitespace().next().map(str::to_owned))\n                .filter(|n| aliases.contains_key(n));\n        }\n    }\n    None\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["cargo","alias","config","recursion","cli"],"analyzedSha":"0e07a155371a6ce88ae53a2c00df940280c09a67","analyzedAt":"2026-08-06T01:46:58.334Z","schemaVersion":2}