{"record":{"id":"54e699185d9c6b8d","repo":"windmill-labs/windmill","slug":"cycle-detected-adding-edge-from-sourceid-to","errorCode":null,"errorMessage":"Cycle detected: adding edge from '${sourceId}' to '${targetId}' would create a cycle.","messagePattern":"Cycle detected: adding edge from '(.+?)' to '(.+?)' would create a cycle\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/graph/graphBuilder.svelte.ts","lineNumber":582,"sourceCode":"\t\t\t\tdisableMoveIds?: string[]\n\t\t\t}\n\t\t) {\n\t\t\tparents[targetId] = [...(parents[targetId] ?? []), sourceId]\n\n\t\t\tlet index: number\n\t\t\tif (options?.currentItems) {\n\t\t\t\tindex = findInsertIndexByNodeId(options.currentItems, targetId)\n\t\t\t} else {\n\t\t\t\tconst mods = options?.subModules ?? modules\n\t\t\t\tconst found = mods?.findIndex((m) => m.id === targetId) ?? -1\n\t\t\t\tindex = found >= 0 ? found : (mods?.length ?? 0)\n\t\t\t}\n\n\t\t\tconst visited = new Set<string>()\n\t\t\tconst recStack = new Set<string>()\n\n\t\t\tif (detectCycle(sourceId, visited, recStack)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Cycle detected: adding edge from '${sourceId}' to '${targetId}' would create a cycle.`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tedges.push({\n\t\t\t\tid: options?.customId || `edge:${sourceId}->${targetId}`,\n\t\t\t\tsource: sourceId,\n\t\t\t\ttarget: targetId,\n\t\t\t\ttype: options?.type ?? 'edge',\n\t\t\t\tdata: {\n\t\t\t\t\tsourceId,\n\t\t\t\t\ttargetId,\n\t\t\t\t\tbranch,\n\t\t\t\t\teventHandlers,\n\t\t\t\t\tsimplifiedTriggerView: simplifiableFlow?.simplifiedFlow,\n\t\t\t\t\tdisableMoveIds: options?.disableMoveIds,\n\t\t\t\t\tenableTrigger: sourceId === 'Input',\n\t\t\t\t\tindex,","sourceCodeStart":564,"sourceCodeEnd":600,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/graph/graphBuilder.svelte.ts#L564-L600","documentation":"addEdge runs a DFS cycle detection before inserting an edge; adding the requested source→target edge would make the flow graph cyclic, which React Flow / flow semantics do not support. The edge is rejected instead of silently creating a loop.","triggerScenarios":"Calling addEdge (directly or via processModules) where targetId is an ancestor of sourceId in the existing edge set — e.g. wiring a later step back to an earlier one, or branch child edges that loop back to the parent.","commonSituations":"Hand-editing flow edges to model loops/branches back-edges; a bug in custom flow generation that emits both forward and backward dependency edges; dragging an edge in the editor from a child step to an ancestor.","solutions":["Remove the edge that loops back to an ancestor step; flows are DAGs.","Restructure the logic with a loop/iterator construct (foreach step) instead of a graph cycle.","If the edge is intentional only for visualization, give it a distinct handling path rather than pushing it as a real edge.","Audit the code that generates edges so dependency order (topological) is respected."],"exampleFix":"// before\naddEdge('step3', 'step1') // creates cycle\n// after\n// model iteration with a foreach step containing step1..step3, no back edge","handlingStrategy":"validation","validationCode":"function wouldCreateCycle(edges, source, target) {\n  const adj = new Map(); edges.forEach(e => adj.set(e.source, [...(adj.get(e.source)||[]), e.target]))\n  const stack = [target]; const seen = new Set()\n  while (stack.length) { const n = stack.pop(); if (n === source) return true; if (seen.has(n)) continue; seen.add(n); stack.push(...(adj.get(n)||[])) }\n  return false\n}","typeGuard":"function isDagAddition(edges, source, target) { return !wouldCreateCycle(edges, source, target) }","tryCatchPattern":"try { addEdge(sourceId, targetId) } catch (e) { if (String(e.message).startsWith('Cycle detected')) toast('Flows cannot contain loops; use a foreach step'); else throw e }","preventionTips":["Only add edges from earlier to later steps (topological order)","Model iteration with foreach/loop constructs, not back edges","Run cycle detection in UI before committing a drag-connect"],"tags":["graph","cycle","dag"],"backgroundTag":"graph-cycle-detected","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}