{"record":{"id":"c552ad6ff8c57579","repo":"ruvnet/ruflo","slug":"worker-dependency-cycle-remaining-join","errorCode":null,"errorMessage":"worker dependency cycle: ${remaining.join(', ')}","messagePattern":"worker dependency cycle: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/dual-mode/orchestrator.ts","lineNumber":422,"sourceCode":"    const placed = new Set<string>();\n\n    while (placed.size < workers.length) {\n      const level: WorkerConfig[] = [];\n\n      for (const worker of workers) {\n        if (placed.has(worker.id)) continue;\n\n        const depsReady = !worker.dependsOn ||\n          worker.dependsOn.every(dep => placed.has(dep));\n\n        if (depsReady) {\n          level.push(worker);\n        }\n      }\n\n      if (level.length === 0 && placed.size < workers.length) {\n        const remaining = workers.filter((worker) => !placed.has(worker.id)).map((worker) => worker.id);\n        throw new Error(`worker dependency cycle: ${remaining.join(', ')}`);\n      }\n\n      for (const worker of level) {\n        placed.add(worker.id);\n      }\n\n      if (level.length > 0) {\n        levels.push(level);\n      }\n    }\n\n    for (const level of levels) {\n      const writerPaths = new Set<string>();\n      const writers = level.filter((item) => !item.readOnly);\n      for (const worker of writers) {\n        if (this.config.worktreeIsolation && !worker.worktreePath) {\n          throw new Error(`writer ${worker.id} requires an isolated worktree`);\n        }","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/dual-mode/orchestrator.ts#L404-L440","documentation":"Thrown by the layered scheduler in DualModeOrchestrator when it topologically sorts WorkerConfig entries by dependsOn. Each pass places workers whose dependencies are already placed; if a pass places zero workers while some remain unplaced, the dependency graph can never be satisfied and the error lists the stuck worker ids. Besides true cycles (A depends on B while B depends on A, or a self-dependency), it also fires when dependsOn names an id that is not present in the workers array, because that dependency is never placed.","triggerScenarios":"Calling the orchestrator run/schedule path with a workers array where (1) two or more workers reference each other in dependsOn, (2) a worker lists its own id in dependsOn, or (3) a dependsOn entry references a typo'd, renamed, or removed worker id that does not exist in the same array.","commonSituations":"Copy-pasted worker configs where ids were renamed but dependsOn strings were not updated; programmatically generated fan-in/fan-out graphs that accidentally close a loop; merging two swarm configs whose workers each depend on the other's workers.","solutions":["Read the worker ids in the error message — they are exactly the workers that could never be scheduled; start there","For each listed worker, trace its dependsOn chain to find the edge that closes the loop (including a self-dependency) and remove or reverse it","Verify every dependsOn entry matches the id of a worker actually present in the same workers array (watch for typos and stale ids after renames)","If worker graphs are generated dynamically, run a cycle/unknown-dep check before handing them to the orchestrator"],"exampleFix":"// before\nconst workers = [\n  { id: 'build', role: 'coder', dependsOn: ['test'] },\n  { id: 'test', role: 'tester', dependsOn: ['build'] }, // build <-> test cycle\n];\n\n// after\nconst workers = [\n  { id: 'build', role: 'coder', dependsOn: [] },\n  { id: 'test', role: 'tester', dependsOn: ['build'] },\n];","handlingStrategy":"validation","validationCode":"function assertWorkersSchedulable(workers: WorkerConfig[]): void {\n  const ids = new Set(workers.map((w) => w.id));\n  for (const w of workers) {\n    for (const dep of w.dependsOn ?? []) {\n      if (!ids.has(dep)) throw new Error(`worker ${w.id} depends on unknown id ${dep}`);\n    }\n  }\n  const placed = new Set<string>();\n  let progress = true;\n  while (progress) {\n    progress = false;\n    for (const w of workers) {\n      if (placed.has(w.id)) continue;\n      if ((w.dependsOn ?? []).every((d) => placed.has(d))) {\n        placed.add(w.id);\n        progress = true;\n      }\n    }\n  }\n  const remaining = workers.filter((w) => !placed.has(w.id)).map((w) => w.id);\n  if (remaining.length) throw new Error(`dependency cycle among: ${remaining.join(', ')}`);\n}","typeGuard":null,"tryCatchPattern":"Catch Error around the orchestrator run; when the message starts with 'worker dependency cycle', parse the trailing id list and abort the swarm start — retrying without editing the dependsOn graph fails identically every time.","preventionTips":["Treat workers as a DAG: single source of truth for ids, with dependsOn values type-checked against it","Run a cycle check in the config loader or test suite, not at orchestration time","Never hand-edit dependsOn strings after renaming worker ids — rename programmatically"],"tags":["orchestration","dependency-cycle","topological-sort","workers","dual-mode"],"backgroundTag":"dependency-cycle-detected","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}