{"record":{"id":"27f890009129ca15","repo":"Yeachan-Heo/oh-my-codex","slug":"team-dag-contains-a-cycle","errorCode":null,"errorMessage":"Team DAG contains a cycle","messagePattern":"Team DAG contains a cycle","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/team/repo-aware-decomposition.ts","lineNumber":136,"sourceCode":"  const outgoing = new Map<string, string[]>();\n  for (const node of nodes) {\n    for (const dep of node.depends_on ?? []) {\n      indegree.set(node.id, (indegree.get(node.id) ?? 0) + 1);\n      outgoing.set(dep, [...(outgoing.get(dep) ?? []), node.id]);\n    }\n  }\n  const ready = nodes.filter((node) => (indegree.get(node.id) ?? 0) === 0);\n  const sorted: TeamDagNode[] = [];\n  while (ready.length > 0) {\n    ready.sort((a, b) => (inputIndex.get(a.id) ?? 0) - (inputIndex.get(b.id) ?? 0));\n    const node = ready.shift()!;\n    sorted.push(node);\n    for (const next of outgoing.get(node.id) ?? []) {\n      indegree.set(next, (indegree.get(next) ?? 0) - 1);\n      if ((indegree.get(next) ?? 0) === 0) ready.push(byId.get(next)!);\n    }\n  }\n  if (sorted.length !== nodes.length) throw new Error('Team DAG contains a cycle');\n  return sorted;\n}\n\nfunction firstReadyLaneCount(nodes: TeamDagNode[]): number {\n  const ready = nodes.filter((node) => (node.depends_on?.length ?? 0) === 0);\n  if (ready.length === 0) return 1;\n  const conflictGroups = new Set<string>();\n  let noFileCount = 0;\n  for (const node of ready) {\n    const files = node.filePaths ?? [];\n    if (files.length === 0) {\n      noFileCount += 1;\n      continue;\n    }\n    conflictGroups.add(files.map(normalizePath).sort().join('|'));\n  }\n  return Math.max(1, conflictGroups.size + noFileCount);\n}","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/team/repo-aware-decomposition.ts#L118-L154","documentation":"topologicalSort implements Kahn's algorithm over the team dependency DAG; if the number of emitted nodes is less than the total node count, at least one node never reached indegree 0, which only happens when the dependency graph has a cycle. The error means the decomposition (depends_on edges) is not a valid DAG.","triggerScenarios":"Passing TeamDagNode[] where A depends_on B, B depends_on C, and C depends_on A (direct cycle), or a self-dependency node.depends_on containing its own id.","commonSituations":"LLM-generated task decompositions that create mutually-dependent tasks; user-authored DAG specs with circular references; duplicate ids causing edges that resolve back onto themselves.","solutions":["Run a cycle check (DFS or Kahn) on depends_on before calling topologicalSort and report the cycle path","Fix or remove the circular depends_on entries in the decomposition","If decompositions are machine-generated, add a post-generation validation step that rejects cycles"],"exampleFix":"// before\nconst sorted = topologicalSort(nodes);\n// after\nfunction findCycle(nodes) { /* DFS with rec-stack, returns cycle ids or null */ }\nconst cycle = findCycle(nodes);\nif (cycle) throw new Error(`cyclic dependency: ${cycle.join(' -> ')}`);\nconst sorted = topologicalSort(nodes);","handlingStrategy":"validation","validationCode":"function assertAcyclic(nodes: TeamDagNode[]): void {\n  const state = new Map<string, 0|1|2>();\n  const dfs = (id: string, stack: string[]) => {\n    const s = state.get(id); if (s === 2) return; if (s === 1) throw new Error('cycle: ' + [...stack, id].join('->'));\n    state.set(id, 1);\n    for (const n of nodes.find(x => x.id === id)?.depends_on ?? []) dfs(n, [...stack, id]);\n    state.set(id, 2);\n  };\n  nodes.forEach(n => dfs(n.id, []));\n}","typeGuard":null,"tryCatchPattern":"try { topologicalSort(nodes); } catch (e) { if (e instanceof Error && e.message === 'Team DAG contains a cycle') { /* surface offending edges to user */ } throw e; }","preventionTips":["Validate machine-generated decompositions for cycles before persisting","Disallow self-references in depends_on at input parse time","Log the full edge set when a cycle is rejected"],"tags":["dag","topological-sort","cycle-detection","dependency-graph"],"backgroundTag":"dependency-cycle-detected","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}