jdx/mise · error

Cycle detected in tool overrides

Error message

Cycle detected in tool overrides

What it means

sort_by_overrides performs a topological sort of tools based on their override dependencies. If the sort finishes but produced fewer nodes than the dependency graph contains, the graph has a cycle, meaning tool override requirements are mutually contradictory. mise cannot determine a valid install/use order and bails.

Source

Thrown at src/toolset/mod.rs:673

        let mut sorted_ids: Vec<&str> = Vec::with_capacity(graph.node_count());
        while let Some(Reverse((_, _, id))) = pq.pop() {
            sorted_ids.push(id);

            for neighbor in graph.neighbors(id) {
                if let Some(deg) = in_degree.get_mut(neighbor) {
                    *deg -= 1;
                    if *deg == 0 {
                        let p = priorities[neighbor];
                        let idx = original_index[neighbor];
                        pq.push(Reverse((p, idx, neighbor)));
                    }
                }
            }
        }

        if sorted_ids.len() != graph.node_count() {
            bail!("Cycle detected in tool overrides");
        }

        let order: HashMap<&str, usize> = sorted_ids
            .iter()
            .enumerate()
            .map(|(i, &id)| (id, i))
            .collect();
        installed.sort_by_cached_key(|(b, _)| order.get(b.id()).copied().unwrap_or(usize::MAX));

        Ok(())
    }

    pub(crate) async fn which(
        &self,
        config: &Arc<Config>,
        bin_name: &str,
    ) -> Option<(Arc<dyn Backend>, ToolVersion)> {
        let mut installed = self.list_current_installed_versions(config);

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Inspect your mise.toml/.config/mise.toml [tools] overrides and remove the circular dependency between tools
  2. Restructure overrides so dependency direction is one-way (e.g. A depends on B only)
  3. Simplify by pinning one tool's version explicitly instead of conditionally via the other
  4. Use `mise ls` and config files in `mise config ls` to find which files contribute the overrides

Example fix

// before (cycle)
[tools.node.overrides]
python = '3.12'
[tools.python.overrides]
node = '22'
// after (one-way)
[tools.python.overrides]
node = '22'
Defensive patterns

Strategy: validation

Validate before calling

// lint config: detect cycles in overrides before running mise
const deps = parseOverrides(config); assertNoCycle(deps);

Type guard

const acyclic = (graph) => { const seen=new Set(), stack=new Set(); const visit=n=>{ if(stack.has(n)) return false; if(seen.has(n)) return true; stack.add(n); for(const m of graph[n]||[]) if(!visit(m)) return false; stack.delete(n); seen.add(n); return true; }; return [...graph].every(([n])=>visit(n)); };

Try / catch

try { loadToolset() } catch (e) { if (String(e).includes('Cycle detected in tool overrides')) { reportConfigError(e); } else { throw e; } }

Prevention

When it happens

Trigger: Calling sort_by_overrides (Toolset construction/config loading) where tool A's overrides require B and B's require A (directly or transitively), causing the topological sort to emit fewer ids than graph.node_count().

Common situations: Misconfigured [tool.overrides] sections in mise.toml where version conditions for two tools each reference the other; nested config files at different directories creating mutual override dependencies.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/5b74dddf4eb3ab73. Report an issue: GitHub.