{"record":{"id":"1b8a2d4969f77979","repo":"jdx/mise","slug":"task-task-name-input-group-cycle","errorCode":null,"errorMessage":"task {task_name} input group cycle: {}","messagePattern":"task (.+?) input group cycle: (.+?)","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/config/mod.rs","lineNumber":2988,"sourceCode":"    for entry in entries {\n        let Some(group) = entry.strip_prefix(TASK_INPUT_GROUP_PREFIX) else {\n            expanded.push(if anchor_literals {\n                anchor_task_input(root, entry)\n            } else {\n                entry.clone()\n            });\n            continue;\n        };\n        let (groups, groups_root) = task_inputs.input_groups.as_ref().ok_or_else(|| {\n            eyre!(\"task {task_name} references undefined input group {group:?} with {entry:?}\")\n        })?;\n        let inputs = groups.get(group).ok_or_else(|| {\n            eyre!(\"task {task_name} references undefined input group {group:?} with {entry:?}\")\n        })?;\n        if let Some(cycle_start) = stack.iter().position(|name| name == group) {\n            let mut cycle = stack[cycle_start..].to_vec();\n            cycle.push(group.to_string());\n            bail!(\n                \"task {task_name} input group cycle: {}\",\n                cycle.iter().join(\" -> \")\n            );\n        }\n        stack.push(group.to_string());\n        expanded.extend(expand_task_inputs(\n            inputs,\n            task_inputs,\n            groups_root,\n            task_name,\n            stack,\n            true,\n        )?);\n        stack.pop();\n    }\n    Ok(expanded)\n}\n","sourceCodeStart":2970,"sourceCodeEnd":3006,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/config/mod.rs#L2970-L3006","documentation":"Thrown while mise expands task `inputs` entries that reference input groups (`@group:<name>`) in mise.toml. `expand_task_inputs` (src/config/mod.rs:2961) keeps a stack of group names currently being expanded; if the same group appears again deeper in the chain (a -> b -> a), expansion could never terminate, so mise bails and prints the full cycle path. This is task-configuration validation, not a failure of the task itself.","triggerScenarios":"A task's `inputs` (or a group's member list) contains `@group:a`, group `a` includes `@group:b`, and group `b` includes `@group:a` again — directly or through more groups. The error fires only when a group name is already on the expansion stack; referencing a group once, or referencing it from two sibling groups, is fine.","commonSituations":"Refactoring shared input lists into groups and accidentally making two groups reference each other; renaming a group so an old name now resolves into a chain that loops back; copy-pasting a group definition and leaving a self/mutual reference; YAML anchors duplicating group references.","solutions":["Read the cycle in the message (e.g. `a -> b -> a`) and open the `[task_inputs.groups.*]` definitions in the config file being loaded","Break the loop by removing the `@group:` reference that closes it (the edge back to the first group in the printed cycle)","If two groups must share content, extract a third base group and have both include it instead of each other","Re-run any task-loading command (e.g. `mise tasks ls`) to confirm the config now parses"],"exampleFix":"# before — cycle: shared -> lint -> shared\n[task_inputs.groups.shared]\ninputs = [\"src/**\", \"@group:lint\"]\n[task_inputs.groups.lint]\ninputs = [\"@group:shared\", \"lint/**\"]\n\n# after — one-directional references only\n[task_inputs.groups.base]\ninputs = [\"src/**\"]\n[task_inputs.groups.shared]\ninputs = [\"@group:base\"]\n[task_inputs.groups.lint]\ninputs = [\"@group:base\", \"lint/**\"]","handlingStrategy":"validation","validationCode":"# pre-commit lint: fail on input-group cycles before mise does\nimport tomllib, sys\ncfg = tomllib.load(open(\"mise.toml\", \"rb\"))\ngroups = cfg.get(\"task_inputs\", {}).get(\"groups\", {})\ndef dfs(g, stack):\n    if g in stack:\n        sys.exit(f\"input group cycle: {' -> '.join(stack[stack.index(g):] + [g])}\")\n    for ref in groups.get(g, []):\n        if isinstance(ref, str) and ref.removeprefix(\"@group:\") in groups:\n            dfs(ref.removeprefix(\"@group:\"), stack + [g])\nfor g in groups:\n    dfs(g, [])","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep group references one-directional: define 'base' groups that others include, never mutual includes","Run `mise tasks ls` (or any task-loading command) in CI so cycle typos fail the build early","When renaming a group, grep for `@group:<old-name>` across all config files before committing"],"tags":["mise","task","config","cycle","inputs"],"backgroundTag":"config-circular-reference","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}