{"record":{"id":"07cf595cbd36f465","repo":"alibaba/spring-ai-alibaba","slug":"workflow-run-cancel","errorCode":"WORKFLOW_RUN_CANCEL","errorMessage":"Manually terminated","messagePattern":"Manually terminated","errorType":"error_code","errorClass":"BizException","httpStatus":null,"severity":"info","filePath":"spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-core/src/main/java/com/alibaba/cloud/ai/studio/core/workflow/processor/AbstractExecuteProcessor.java","lineNumber":181,"sourceCode":"\t\t\t// Node execution error, save execution result and record error information\n\t\t\tNodeResult errorNodeResult = NodeResult.error(node, e.getMessage());\n\t\t\terrorNodeResult.setInput(JsonUtils.toJson(constructInputParamsMap(node, context)));\n\t\t\thandleNodeResult(graph, node, context, errorNodeResult, start);\n\t\t}\n\t}\n\n\t/**\n\t * Pre-execution validation checks. Verifies: 1. Workflow is not stopped 2. Node has\n\t * valid successors (unless it's an end node)\n\t * @param graph The workflow graph\n\t * @param node The node to validate\n\t * @param context The workflow context\n\t * @throws BizException if validation fails\n\t */\n\tpublic void preCheck(DirectedAcyclicGraph<String, Edge> graph, Node node, WorkflowContext context) {\n\t\t// boolean b = workflowInnerService.checkValidFlag(context);\n\t\tif (context.getTaskStatus().equals(NodeStatusEnum.STOP.getCode())) {\n\t\t\tthrow new BizException(ErrorCode.WORKFLOW_RUN_CANCEL.toError(\"Manually terminated\"));\n\t\t}\n\t\tif ((!node.getId().startsWith(\"End_\") && !node.getId().startsWith(\"IteratorEnd_\")\n\t\t\t\t&& !node.getId().startsWith(\"ParallelEnd_\"))\n\t\t\t\t&& CollectionUtils.isEmpty(graph.outgoingEdgesOf(node.getId()))) {\n\t\t\tthrow new BizException(ErrorCode.WORKFLOW_CONFIG_INVALID\n\t\t\t\t.toError(\"the current node has no successor node, and it cannot function properly.\"));\n\t\t}\n\t}\n\n\t/**\n\t * Core execution logic to be implemented by concrete processors.\n\t * @param graph The workflow graph\n\t * @param node The node to execute\n\t * @param context The workflow context\n\t * @return NodeResult containing execution results\n\t */\n\tpublic abstract NodeResult innerExecute(DirectedAcyclicGraph<String, Edge> graph, Node node,\n\t\t\tWorkflowContext context);","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-core/src/main/java/com/alibaba/cloud/ai/studio/core/workflow/processor/AbstractExecuteProcessor.java#L163-L199","documentation":"AbstractExecuteProcessor.preCheck() runs before each workflow node executes. If the workflow context's task status is NodeStatusEnum.STOP, it throws a BizException with code WORKFLOW_RUN_CANCEL to abort the run — the workflow was manually terminated by a user.","triggerScenarios":"A user pressed stop/cancel on a running workflow (task status set to STOP in the context), and the next node's execute() calls preCheck() before running.","commonSituations":"Long-running workflow executions cancelled from the Studio/Admin UI; concurrent cancel requests racing node execution.","solutions":["Treat this as expected control flow: catch BizException with code WORKFLOW_RUN_CANCEL and mark the run as cancelled rather than failed","Ensure the UI only sends cancel when the run is active, and update task status idempotently","If it fires unexpectedly, check whether some service is writing NodeStatusEnum.STOP into the context erroneously"],"exampleFix":"// before\ncatch (BizException e) { log.error(\"node failed\", e); }\n// after\ncatch (BizException e) {\n  if (e.getCode() == ErrorCode.WORKFLOW_RUN_CANCEL.getCode()) {\n    context.setTaskStatus(NodeStatusEnum.STOP.getCode()); return; // expected cancel\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (context.getTaskStatus() == NodeStatusEnum.STOP.getCode()) {\n    // skip node execution proactively\n    return;\n}","typeGuard":"static boolean isCancelled(WorkflowContext ctx) {\n    return ctx != null && NodeStatusEnum.STOP.getCode().equals(ctx.getTaskStatus());\n}","tryCatchPattern":"try {\n    processor.execute(graph, node, context);\n} catch (BizException e) {\n    if (ErrorCode.WORKFLOW_RUN_CANCEL.getCode().equals(e.getCode())) {\n        log.info(\"Workflow {} cancelled by user\", context.getRequestId());\n        return;\n    }\n    throw e;\n}","preventionTips":["Check task status at loop boundaries before each node","Handle WORKFLOW_RUN_CANCEL explicitly as control flow, not as failure","Make cancel-state writes idempotent so status converges quickly"],"tags":["workflow","cancellation","biz-exception"],"backgroundTag":"invalid-state-transition","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}