{"record":{"id":"417de95084de12ea","repo":"affaan-m/ECC","slug":"dependency-cycle","errorCode":null,"errorMessage":"Dependency cycle","messagePattern":"Dependency cycle","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/workflow_graphs.py","lineNumber":90,"sourceCode":"\ndef validate_graph(graph):\n    \"\"\"Verify dependencies and every declared input; endpoint schemas need live QA.\"\"\"\n    contents = graph['contents']\n    nodes, inputs = contents['nodes'], contents['schema']['input']\n    used = set()\n    for name, node in nodes.items():\n        if node['id'] != name:\n            raise ValueError(f'Node id mismatch: {name}')\n        for dependency in node.get('depends', []):\n            if dependency != 'input' and dependency not in nodes:\n                raise ValueError(f'Unknown dependency: {dependency}')\n    ancestors = {}\n\n    def visit(name, stack):\n        if name == 'input':\n            return set()\n        if name in stack:\n            raise ValueError('Dependency cycle')\n        if name not in ancestors:\n            deps = nodes[name].get('depends', [])\n            ancestors[name] = set(deps).union(*(visit(dep, stack | {name}) for dep in deps))\n        return ancestors[name]\n\n    for name, node in nodes.items():\n        reachable = visit(name, set())\n        for ref in references_in({'input': node.get('input'), 'fields': node.get('fields')}):\n            if ref[0] not in reachable:\n                raise ValueError(f'{name} references undeclared dependency: {ref[0]}')\n            if ref[0] == 'input':\n                if len(ref) < 2 or ref[1] not in inputs:\n                    raise ValueError('Unknown workflow input')\n                used.add(ref[1])\n    for ref in references_in(contents.get('output', {})):\n        if ref[0] not in nodes:\n            raise ValueError('Unknown output node')\n    if used != set(inputs):","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/workflow_graphs.py#L72-L108","documentation":"validate_graph walks each node's dependency chain recursively via visit(); when a node name is already on the current traversal stack, the graph contains a circular depends relationship, so ValueError('Dependency cycle') is raised to prevent infinite recursion and an unsatisfiable workflow ordering.","triggerScenarios":"A taste-*.json workflow graph defines nodes where node A depends on B and B (directly or transitively) depends again on A — e.g. 'apply' depends on 'distill' and 'distill' depends on 'apply'.","commonSituations":"Hand-editing a workflow JSON and adding a depends entry to reuse another node's output; copy-pasting node definitions between workflows so mutual references survive; renaming nodes so an intended dependency now resolves to an ancestor.","solutions":["Inspect the workflow JSON's node depends arrays and remove the edge that closes the loop (typically the most recently added one)","Topologically list intended stages on paper (distill -> apply) and confirm each depends entry only points to earlier stages","Rename a node instead of re-declaring it if the cycle came from duplicating an existing stage to add a variant","If a shared computation is needed by two branches, extract it into a third node that both depend on rather than making the branches depend on each other"],"exampleFix":"// before (cycle)\n{\"nodes\": {\"apply\": {\"depends\": [\"distill\"]}, \"distill\": {\"depends\": [\"apply\"]}}}\n// after (acyclic)\n{\"nodes\": {\"distill\": {\"depends\": []}, \"apply\": {\"depends\": [\"distill\"]}}}","handlingStrategy":"validation","validationCode":"def is_acyclic(nodes):\n    state = {}\n    def dfs(n):\n        if state.get(n) == 1: return False\n        if state.get(n) == 2: return True\n        state[n] = 1\n        for d in nodes[n].get('depends', []):\n            if not dfs(d): return False\n        state[n] = 2\n        return True\n    return all(dfs(n) for n in nodes)","typeGuard":null,"tryCatchPattern":"try:\n    graph = load_graph(kind)\nexcept ValueError as e:\n    if 'Dependency cycle' in str(e):\n        # surface the offending workflow file and abort config load\n        ...","preventionTips":["Keep workflows strictly staged: each node depends only on earlier stages","Lint workflow JSONs with a topological sort in CI before shipping","Avoid copy-pasting node blocks between workflow files without re-checking depends"],"tags":["graph","configuration","cycle-detection"],"backgroundTag":"invalid-state-transition","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}