{"record":{"id":"c3f088784ece8574","repo":"alibaba/spring-ai-alibaba","slug":"cannot-create-update-map-after-mergeall-has-been","errorCode":null,"errorMessage":"Cannot create update map after mergeAll() has been called","messagePattern":"Cannot create update map after mergeAll\\(\\) has been called","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tool/ToolStateCollector.java","lineNumber":96,"sourceCode":"\t * @param totalTools the total number of tools to collect state from\n\t * @param keyStrategies the key strategies for merging (nullable)\n\t */\n\tpublic ToolStateCollector(int totalTools, Map<String, KeyStrategy> keyStrategies) {\n\t\tthis.totalTools = totalTools;\n\t\tthis.keyStrategies = keyStrategies != null ? keyStrategies : Collections.emptyMap();\n\t}\n\n\t/**\n\t * Creates an isolated update map for a tool at the given index. The returned map is a\n\t * ConcurrentHashMap, allowing safe concurrent writes even if the tool implementation\n\t * has internal async operations.\n\t * @param index the tool index (0-based, in original toolCalls order)\n\t * @return a new ConcurrentHashMap for the tool to write updates to\n\t * @throws IllegalStateException if mergeAll() has already been called\n\t */\n\tpublic Map<String, Object> createToolUpdateMap(int index) {\n\t\tif (merged.get()) {\n\t\t\tthrow new IllegalStateException(\"Cannot create update map after mergeAll() has been called\");\n\t\t}\n\t\t// Using ConcurrentHashMap to support tools with internal async operations\n\t\tMap<String, Object> updateMap = new ConcurrentHashMap<>();\n\t\ttoolUpdatesByIndex.put(index, updateMap);\n\t\treturn updateMap;\n\t}\n\n\t/**\n\t * Discards updates for a tool at the given index.\n\t *\n\t * <p>\n\t * This is useful when a tool execution times out and we want to avoid merging partial\n\t * updates that may still be written after the timeout.\n\t * </p>\n\t * @param index the tool index (0-based, in original toolCalls order)\n\t */\n\tpublic void discardToolUpdateMap(int index) {\n\t\ttoolUpdatesByIndex.remove(index);","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tool/ToolStateCollector.java#L78-L114","documentation":"ToolStateCollector.createToolUpdateMap() throws IllegalStateException \"Cannot create update map after mergeAll() has been called\" because once results are merged, allowing new update maps would produce inconsistent state. The collector uses an AtomicBoolean to enforce the create-then-merge lifecycle.","triggerScenarios":"Calling createToolUpdateMap(index) after mergeAll() has already been invoked on the same collector — typically a tool callback that fires late (async completion after merge) or duplicated registration.","commonSituations":"Parallel tool execution where a slow async tool writes back after the orchestrator already called mergeAll(); code that reuses a collector across rounds; tests invoking create after merge.","solutions":["Ensure mergeAll() is called only after all tool update maps have been created and populated (join all futures first).","Create all update maps up front, before dispatching tools, and hand them to the tools.","Use a fresh ToolStateCollector per tool-execution round.","Guard late async writes with a check or cancel the token before merging."],"exampleFix":"// before\nMap<String,Object> m = collector.createToolUpdateMap(0); // after mergeAll() -> IllegalStateException\n// after\nMap<String,Object> m = collector.createToolUpdateMap(0);\nrunTool(m);\nMap<String,Object> merged = collector.mergeAll();","handlingStrategy":"validation","validationCode":"// before creating maps\nif (collectorMerged(collector)) { throw new IllegalStateException(\"collector already merged\"); }","typeGuard":null,"tryCatchPattern":"try { Map<String,Object> m = collector.createToolUpdateMap(i); } catch (IllegalStateException e) { /* recreate collector and re-run round */ }","preventionTips":["Create all update maps before dispatching tools","Join all async tool futures before mergeAll()","One collector per execution round","Cancel tokens before merging to stop late writes"],"tags":["state","lifecycle","illegal-state","parallel-tools"],"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"}