{"record":{"id":"df482f93e2ad0878","repo":"alibaba/spring-ai-alibaba","slug":"interruption-after-on-subgraph-is-not-supported","errorCode":null,"errorMessage":"'interruption after' on subgraph is not supported yet! consider to use 'interruption before' node: '%s'","messagePattern":"'interruption after' on subgraph is not supported yet! consider to use 'interruption before' node: '(.+?)'","errorType":"exception","errorClass":"GraphStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/ProcessedNodesEdgesAndConfig.java","lineNumber":143,"sourceCode":"\t\t\t// Process END Nodes\n\t\t\t//\n\t\t\tvar sgEdgesEnd = processedSubGraphEdges.edgesByTargetId(END);\n\n\t\t\tvar edgeWithSubgraphSourceId = edges.edgeBySourceId(subgraphNode.id()).orElseThrow();\n\n\t\t\tif (edgeWithSubgraphSourceId.isParallel()) {\n\t\t\t\tthrow new GraphStateException(\"subgraph not support routes to parallel branches yet!\");\n\t\t\t}\n\n\t\t\t// Process Interruption (After) Subgraph(s)\n\t\t\tif (interruptsAfter.contains(subgraphNode.id())) {\n\n\t\t\t\tvar exceptionMessage = (edgeWithSubgraphSourceId.target()\n\t\t\t\t\t\t.id() == null) ? \"'interruption after' on subgraph is not supported yet!\"\n\t\t\t\t\t\t\t\t: format(\n\t\t\t\t\t\t\t\t\t\t\"'interruption after' on subgraph is not supported yet! consider to use 'interruption before' node: '%s'\",\n\t\t\t\t\t\t\t\t\t\tedgeWithSubgraphSourceId.target().id());\n\t\t\t\tthrow new GraphStateException(exceptionMessage);\n\t\t\t}\n\n\t\t\tsgEdgesEnd.stream()\n\t\t\t\t\t.map(e -> e.withSourceAndTargetIdsUpdated(subgraphNode, subgraphNode::formatId,\n\t\t\t\t\t\t\tid -> (Objects.equals(id, END) ? edgeWithSubgraphSourceId.target()\n\t\t\t\t\t\t\t\t\t: new EdgeValue(subgraphNode.formatId(id)))))\n\t\t\t\t\t.forEach(edges.elements::add);\n\t\t\tedges.elements.remove(edgeWithSubgraphSourceId);\n\n\t\t\t//\n\t\t\t// Process edges\n\t\t\t//\n\t\t\tprocessedSubGraphEdges.elements.stream()\n\t\t\t\t\t.filter(e -> !Objects.equals(e.sourceId(), START))\n\t\t\t\t\t.filter(e -> !e.anyMatchByTargetId(END))\n\t\t\t\t\t.map(e -> e.withSourceAndTargetIdsUpdated(subgraphNode, subgraphNode::formatId,\n\t\t\t\t\t\t\tid -> new EdgeValue(subgraphNode.formatId(id))))\n\t\t\t\t\t.forEach(edges.elements::add);","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/ProcessedNodesEdgesAndConfig.java#L125-L161","documentation":"interruptsAfter (interruption after node execution) is not implemented for subgraph nodes. When the parent graph requests an interrupt after a subgraph node, the library throws this GraphStateException, suggesting 'interruption before' on the following node instead.","triggerScenarios":"Calling compile with interruptsAfter (or addInterruptAfter) containing the id of a node that is a subgraph, e.g. .compile(interruptsAfter(Set.of(\"sub\"))) where \"sub\" is an embedded StateGraph.","commonSituations":"Developers porting plain-node interrupt configs to graphs where some nodes were replaced by subgraphs; human-in-the-loop setups pausing right after a subgraph completes.","solutions":["Replace interrupt-after on the subgraph with interrupt-before on the node that follows it: .compile(interruptsBefore(Set.of(\"nextNode\")))","Add a no-op marker node right after the subgraph and interrupt before that node","Move the interruption logic inside the subgraph itself if applicable","If the edge target has an id, the message names it — use that node in interruptsBefore"],"exampleFix":"// before\napp = outer.compile(interruptsAfter(Set.of(\"sub\"), saver));\n// after\napp = outer.compile(interruptsBefore(Set.of(\"review\"), saver)); // 'review' is the node after 'sub'","handlingStrategy":"validation","validationCode":"Set<String> subgraphIds = parent.subgraphNodeIds();\nif (interruptsAfter.stream().anyMatch(subgraphIds::contains))\n    throw new IllegalStateException(\"use interruptsBefore on the successor node instead\");","typeGuard":null,"tryCatchPattern":"try { app = outer.compile(interruptsAfter(set, saver)); } catch (GraphStateException e) { if (e.getMessage().contains(\"interruption after' on subgraph\")) { /* switch to interruptsBefore on next node */ } else throw e; }","preventionTips":["Prefer interrupt-before for human-in-the-loop checkpoints after subgraphs","Model interrupts inside the subgraph when possible","Read the message: it names the successor node to interrupt before"],"tags":["graph","subgraph","interrupt","unsupported"],"backgroundTag":"unsupported-operation","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"}