sinelaw/fresh · error
Plugin dependency cycle detected among
Error message
Plugin dependency cycle detected among: {}. These plugins will not be loaded. What it means
After Kahn's algorithm, any plugin not placed into the result must be part of (or reachable only through) a dependency cycle; `topological_sort_plugins` reports those plugin names and refuses to load any of them, since load order cannot be established.
Solutions
- Break the cycle among the listed plugins by removing one dependency edge in config/plugin metadata.
- Remove one of the mutually-dependent plugins if the dependency is unnecessary.
- If self-dependency, drop the plugin's reference to itself.
Example fix
// before
{name = "a", deps = ["b"]}
{name = "b", deps = ["a"]}
// after
{name = "a", deps = ["b"]}
{name = "b", deps = []} Defensive patterns
Strategy: validation
Validate before calling
// Detect cycles before sorting (simple DFS)
fn has_cycle(plugins: &[Plugin]) -> bool {
fn visit(n: &str, deps: &HashMap<&str, Vec<&str>>, seen: &mut HashSet<&str>, stack: &mut HashSet<&str>) -> bool {
if stack.contains(n) { return true; }
if !seen.insert(n) { return false; }
stack.insert(n);
deps.get(n).map_or(false, |ds| ds.iter().any(|d| visit(d, deps, seen, stack)))
|| { stack.remove(n); false }
}
let deps: HashMap<&str, Vec<&str>> = plugins.iter().map(|p| (p.name.as_str(), p.deps.iter().map(|s| s.as_str()).collect())).collect();
let mut seen = HashSet::new();
plugins.iter().any(|p| visit(&p.name, &deps, &mut seen, &mut HashSet::new()))
} Try / catch
match topological_sort_plugins(&plugins) {
Err(e) if e.to_string().contains("dependency cycle") => {
log::error!("cycle: {e}");
Vec::new() // load none, keep editor running
}
other => other?,
} Prevention
- Run a cycle check in config validation at startup.
- Keep dependency graphs one-directional: base plugins never depend on feature plugins.
- Add unit tests for each plugin's dependency list.
When it happens
Trigger: Calling `topological_sort_plugins` where plugin dependencies form a cycle (A→B→A, or a self-dependency A→A), detected by leftover nodes with nonzero in-degree.
Common situations: Two plugins each listing the other as a dependency after config edits; a plugin accidentally depending on itself; merging configs that introduced a circular chain.
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
- Plugin ' ' depends on ' ', which is not installed or not…
- Could not determine cache directory
- editor.spawnHostProcess is not implemented (missing…
- editor.spawnProcess is not implemented (missing…
- Failed to read
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/2899da54693e4a66.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-parser-js/src/lib.rs:273
}
}
}
// Sort newly ready plugins alphabetically for determinism
newly_ready.sort();
queue.extend(newly_ready);
queue.sort(); // maintain overall alphabetical order among ready nodes
}
}
if result.len() != plugin_names.len() {
// Some plugins are in a cycle — find them
let in_result: HashSet<&str> = result.iter().map(|s| s.as_str()).collect();
let cycle_plugins: Vec<String> = plugin_names
.iter()
.filter(|n| !in_result.contains(n.as_str()))
.cloned()
.collect();
return Err(anyhow!(
"Plugin dependency cycle detected among: {}. These plugins will not be loaded.",
cycle_plugins.join(", ")
));
}
Ok(result)
}
/// Module metadata for scoped bundling
#[derive(Debug, Clone)]
struct ModuleMetadata {
/// Canonical path to this module
path: PathBuf,
/// Variable name for this module's exports (e.g., "__mod_panel_manager")
var_name: String,
/// Named imports from other modules
imports: Vec<ImportBinding>,
/// Named exports from this moduleView on GitHub (pinned to 67894ca546)