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
- Inspect your mise.toml/.config/mise.toml [tools] overrides and remove the circular dependency between tools
- Restructure overrides so dependency direction is one-way (e.g. A depends on B only)
- Simplify by pinning one tool's version explicitly instead of conditionally via the other
- 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
- Keep override dependencies one-directional
- Avoid declaring reciprocal overrides across global and project configs
- Pin one side explicitly instead of mutually conditioning versions
- Review [tools] overrides after adding new config files
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
- {bin_name} is a mise bin however it is not currently active.
- [dotfiles]."{}": source does not exist: {}
- [dotfiles]."{}": mode symlink-each requires a directory sour
- [dotfiles]."{}": target is not a safe OCI path
- copy image path {image_path:?} ends with `/` but {} is not a
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/5b74dddf4eb3ab73.
Report an issue: GitHub.