affaan-m/ECC · critical · Error
Circular install dependency detected at ${moduleId}
Error message
Circular install dependency detected at ${moduleId} What it means
Thrown by resolveModule() when re-entering a module id that is already in the visitingIds set (a DFS stack-marker). This means the dependency graph contains a cycle: A depends on B depends on ... depends on A. The resolver refuses to infinite-loop and reports the id where the cycle was detected. This is a manifest data bug, not a user-input bug.
Source
Thrown at scripts/lib/install-manifests.js:645
readModuleTargetsOrThrow(module).includes(target)
&& (!targetAdapter || targetAdapter.supportsModule(module, targetPlanningInput))
);
if (!supportsTarget) {
if (dependencyOf) {
skippedTargetIds.add(rootRequesterId || dependencyOf);
return false;
}
skippedTargetIds.add(moduleId);
return false;
}
if (resolvedIds.has(moduleId)) {
return true;
}
if (visitingIds.has(moduleId)) {
throw new Error(`Circular install dependency detected at ${moduleId}`);
}
visitingIds.add(moduleId);
for (const dependencyId of module.dependencies) {
const dependencyResolved = resolveModule(
dependencyId,
moduleId,
rootRequesterId || moduleId
);
if (!dependencyResolved) {
visitingIds.delete(moduleId);
if (!dependencyOf) {
skippedTargetIds.add(moduleId);
}
return false;
}
}
visitingIds.delete(moduleId);View on GitHub (pinned to 01e15490f0)
Solutions
- Inspect module.dependencies in install-modules.json for the offending id and break the cycle.
- Use a topological-sort check (or the project's manifest tests) to catch cycles early.
- git checkout HEAD -- manifests/install-modules.json to restore a known-good graph.
- If you maintain modules, ensure dependencies form a DAG; document the intended order.
Example fix
// before — install-modules.json
{ "id": "a", "dependencies": ["b"] }
{ "id": "b", "dependencies": ["a"] }
// after — break the cycle
{ "id": "a", "dependencies": ["b"] }
{ "id": "b", "dependencies": [] } Defensive patterns
Strategy: validation
Validate before calling
function assertAcyclic(manifests) {
const WHITE = 0, GRAY = 1, BLACK = 2;
const color = new Map(manifests.modules.map(m => [m.id, WHITE]));
function visit(id) {
color.set(id, GRAY);
for (const dep of manifests.modulesById.get(id).dependencies || []) {
if (color.get(dep) === GRAY) throw new Error(`Cycle: ${dep}`);
if (color.get(dep) === WHITE) visit(dep);
}
color.set(id, BLACK);
}
manifests.modules.forEach(m => { if (color.get(m.id) === WHITE) visit(m.id); });
}
assertAcyclic(loadInstallManifests(options)); Prevention
- Treat the dependency graph as a DAG — never introduce a back-edge when editing dependencies.
- Add a topological-sort test in CI to catch cycles before release.
When it happens
Trigger: install-modules.json declares module A with dependencies: ['B'] and module B with dependencies: ['A']. Any selection that reaches either module triggers it.
Common situations: A fork edited dependency arrays and introduced a back-edge; a refactor split a module into two with mutual references; modules were merged without updating dependencies.
Related errors
- Unknown install module: ${moduleId}
- Unknown install component: ${componentId}
- Module ${dependencyOf} depends on excluded module ${moduleId
- Failed to parse ${label} at ${filePath}: ${error.message}
- Invalid ${label} at ${filePath}: expected a JSON object
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/08ab7f118ba59b09.
Report an issue: GitHub.