affaan-m/ECC · error
Circular install dependency detected at
Error message
Circular install dependency detected at ${moduleId} What it means
resolveInstallPlan detects cycles while depth-first resolving module dependencies: it marks modules in `visitingIds` and throws if it re-enters a module already on the current DFS path. This is a manifest data integrity error — a valid module graph must be acyclic.
Solutions
- Open manifests/install-modules.json and trace the dependency cycle starting at moduleId
- Break the cycle by removing one of the `dependencies` entries on the loop
- Run the install plan tests (`npm test`) to confirm the graph resolves after the edit
Example fix
// before (manifests/install-modules.json)
{ "id": "skills-a", "dependencies": ["skills-b"] }
{ "id": "skills-b", "dependencies": ["skills-a"] }
// after
{ "id": "skills-a", "dependencies": ["skills-b"] }
{ "id": "skills-b", "dependencies": [] } Defensive patterns
Strategy: validation
Validate before calling
// Detect cycles in the module graph before resolving
function hasCycle(modulesById, startId) {
const seen = new Set();
function visit(id, stack) {
if (stack.has(id)) return true;
if (seen.has(id)) return false;
stack.add(id);
for (const d of modulesById.get(id)?.dependencies || []) if (visit(d, stack)) return true;
stack.delete(id); seen.add(id);
return false;
}
return visit(startId, new Set());
} Try / catch
try {
const plan = resolveInstallPlan(options);
} catch (e) {
if (/Circular install dependency/.test(e.message)) {
// fix manifests/install-modules.json to remove the cycle
} else throw e;
} Prevention
- Keep dependency direction one-way: lower-level modules must never depend on higher-level ones
- Add a CI test that asserts the module graph is acyclic
- After any manual edit to dependencies, run the manifest tests immediately
When it happens
Trigger: Calling resolveInstallPlan (via plan or runTests) when manifests/install-modules.json defines a dependency cycle, e.g. module A lists B in `dependencies` while B lists A (or a longer loop returns to A).
Common situations: Hand-editing manifests/install-modules.json and adding a two-way dependency; a refactor merged two modules but left cross-references; an automated script rewired dependencies incorrectly.
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
- Module depends on excluded module )` : ''}
- Unknown install module
- A normalized install request is required
- Ambiguous duplicate bundle request
- An unqualified or incompatible Nasiko executable or receipt…
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/08ab7f118ba59b09.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install-manifests.js:670
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 8321021c54)