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

  1. Inspect module.dependencies in install-modules.json for the offending id and break the cycle.
  2. Use a topological-sort check (or the project's manifest tests) to catch cycles early.
  3. git checkout HEAD -- manifests/install-modules.json to restore a known-good graph.
  4. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/08ab7f118ba59b09. Report an issue: GitHub.