{"record":{"id":"08ab7f118ba59b09","repo":"affaan-m/ECC","slug":"circular-install-dependency-detected-at-moduleid","errorCode":null,"errorMessage":"Circular install dependency detected at ${moduleId}","messagePattern":"Circular install dependency detected at (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"scripts/lib/install-manifests.js","lineNumber":645,"sourceCode":"        readModuleTargetsOrThrow(module).includes(target)\n        && (!targetAdapter || targetAdapter.supportsModule(module, targetPlanningInput))\n      );\n\n    if (!supportsTarget) {\n      if (dependencyOf) {\n        skippedTargetIds.add(rootRequesterId || dependencyOf);\n        return false;\n      }\n      skippedTargetIds.add(moduleId);\n      return false;\n    }\n\n    if (resolvedIds.has(moduleId)) {\n      return true;\n    }\n\n    if (visitingIds.has(moduleId)) {\n      throw new Error(`Circular install dependency detected at ${moduleId}`);\n    }\n\n    visitingIds.add(moduleId);\n    for (const dependencyId of module.dependencies) {\n      const dependencyResolved = resolveModule(\n        dependencyId,\n        moduleId,\n        rootRequesterId || moduleId\n      );\n      if (!dependencyResolved) {\n        visitingIds.delete(moduleId);\n        if (!dependencyOf) {\n          skippedTargetIds.add(moduleId);\n        }\n        return false;\n      }\n    }\n    visitingIds.delete(moduleId);","sourceCodeStart":627,"sourceCodeEnd":663,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install-manifests.js#L627-L663","documentation":"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.","triggerScenarios":"install-modules.json declares module A with dependencies: ['B'] and module B with dependencies: ['A']. Any selection that reaches either module triggers it.","commonSituations":"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.","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."],"exampleFix":"// before — install-modules.json\n{ \"id\": \"a\", \"dependencies\": [\"b\"] }\n{ \"id\": \"b\", \"dependencies\": [\"a\"] }\n// after — break the cycle\n{ \"id\": \"a\", \"dependencies\": [\"b\"] }\n{ \"id\": \"b\", \"dependencies\": [] }","handlingStrategy":"validation","validationCode":"function assertAcyclic(manifests) {\n  const WHITE = 0, GRAY = 1, BLACK = 2;\n  const color = new Map(manifests.modules.map(m => [m.id, WHITE]));\n  function visit(id) {\n    color.set(id, GRAY);\n    for (const dep of manifests.modulesById.get(id).dependencies || []) {\n      if (color.get(dep) === GRAY) throw new Error(`Cycle: ${dep}`);\n      if (color.get(dep) === WHITE) visit(dep);\n    }\n    color.set(id, BLACK);\n  }\n  manifests.modules.forEach(m => { if (color.get(m.id) === WHITE) visit(m.id); });\n}\nassertAcyclic(loadInstallManifests(options));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["install","manifest","integrity","cycle","dependency-resolution"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}