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

  1. Open manifests/install-modules.json and trace the dependency cycle starting at moduleId
  2. Break the cycle by removing one of the `dependencies` entries on the loop
  3. 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

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


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)