{"record":{"id":"88ec1b5a03a8d85d","repo":"apache/dolphinscheduler","slug":"workflow-node-has-cycle","errorCode":"WORKFLOW_NODE_HAS_CYCLE","errorMessage":"WORKFLOW_NODE_HAS_CYCLE: workflow node has cycle","messagePattern":"WORKFLOW_NODE_HAS_CYCLE: workflow node has cycle","errorType":"error_code","errorClass":"ServiceException","httpStatus":null,"severity":"error","filePath":"dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java","lineNumber":955,"sourceCode":"            if (workflowTaskRelationJson == null) {\n                log.error(\"workflow task relation data is null.\");\n                throw new ServiceException(Status.DATA_IS_NOT_VALID, \"null\");\n            }\n\n            List<WorkflowTaskRelation> taskRelationList =\n                    JSONUtils.toList(workflowTaskRelationJson, WorkflowTaskRelation.class);\n            // Check whether the task node is normal\n            List<TaskNode> taskNodes = processService.transformTask(taskRelationList, taskDefinitionLogsList);\n\n            if (CollectionUtils.isEmpty(taskNodes)) {\n                log.error(\"Task node data is empty.\");\n                throw new ServiceException(Status.WORKFLOW_DAG_IS_EMPTY);\n            }\n\n            // check has cycle\n            if (graphHasCycle(taskNodes)) {\n                log.error(\"workflow DAG has cycle.\");\n                throw new ServiceException(Status.WORKFLOW_NODE_HAS_CYCLE);\n            }\n\n            // check whether the workflow definition json is normal\n            for (TaskNode taskNode : taskNodes) {\n                if (!checkTaskParameters(taskNode.getType(), taskNode.getParams())) {\n                    throw new ServiceException(Status.WORKFLOW_NODE_S_PARAMETER_INVALID, taskNode.getName());\n                }\n\n                // check extra params\n                CheckUtils.checkOtherParams(taskNode.getExtras());\n            }\n        } catch (ServiceException e) {\n            throw e;\n        } catch (Exception e) {\n            log.error(Status.INTERNAL_SERVER_ERROR_ARGS.getMsg(), e);\n            throw new ServiceException(Status.INTERNAL_SERVER_ERROR_ARGS, e.getMessage());\n        }\n    }","sourceCodeStart":937,"sourceCodeEnd":973,"githubUrl":"https://github.com/apache/dolphinscheduler/blob/02eac45a1b6676e639fcbfb4be2243de5771b05d/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java#L937-L973","documentation":"Thrown by checkWorkflowJsonValidation when graphHasCycle(taskNodes) detects a dependency cycle among the workflow's task nodes. A valid workflow DAG must be acyclic; a cycle would make topological scheduling impossible. The error names the offending workflow implicitly via the request context.","triggerScenarios":"Saving/updating/importing a workflow where task A depends on B and B (transitively) depends on A — i.e. the preTaskNode/postTaskNode relations in taskRelationJson form a directed cycle.","commonSituations":"Hand-editing task relations and creating circular pre-task references; importing workflows modified externally; copy-paste mistakes in taskRelationJson postTaskNodeList values; programmatic DAG generation bugs.","solutions":["Inspect each task's preTasks/postTasks in the workflow JSON and remove the dependency edge that closes the cycle.","Redraw the workflow in the UI designer, which prevents connecting tasks in a cycle, and re-save.","Write a client-side topological sort (Kahn's algorithm) check over taskRelations before calling the API.","If imported, fix the source export and re-import."],"exampleFix":"// before (cycle)\ntaskA.setPreTasks(Lists.newArrayList(\"taskB\"));\ntaskB.setPreTasks(Lists.newArrayList(\"taskA\"));\n// after (acyclic)\ntaskA.setPreTasks(Collections.emptyList());\ntaskB.setPreTasks(Lists.newArrayList(\"taskA\"));","handlingStrategy":"validation","validationCode":"// Kahn's algorithm guard before calling the API\nMap<String, List<String>> adj = buildAdjacency(taskRelations);\nDeque<String> ready = nodesWithZeroIndegree(adj);\nint removed = 0;\nwhile (!ready.isEmpty()) { String n = ready.poll(); removed++; for (String m : adj.get(n)) if (--indeg(m) == 0) ready.add(m); }\nif (removed < adj.size()) throw new IllegalStateException(\"workflow task relations contain a cycle\");","typeGuard":null,"tryCatchPattern":"try {\n    saveWorkflow(json);\n} catch (ServiceException e) {\n    if (e.getCode() == Status.WORKFLOW_NODE_HAS_CYCLE.getCode()) {\n        log.error(\"cyclic preTask references in workflow {}\", name);\n    } else throw e;\n}","preventionTips":["Maintain preTasks references by task name/code generated from one source of truth","Run a topological-sort check on any programmatically generated DAG before saving","Avoid hand-editing taskRelationJson; regenerate it from task definitions"],"tags":["workflow-definition","dag","cycle-detection"],"backgroundTag":"invalid-argument-value","analyzedSha":"02eac45a1b6676e639fcbfb4be2243de5771b05d","analyzedAt":"2026-09-06T17:43:00.555Z","contentChangedAt":"2026-09-06T17:43:00.555Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}