alibaba/spring-ai-alibaba · error · IllegalStateException
Cannot create update map after mergeAll() has been called
Error message
Cannot create update map after mergeAll() has been called
What it means
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.
Source
Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tool/ToolStateCollector.java:96
* @param totalTools the total number of tools to collect state from
* @param keyStrategies the key strategies for merging (nullable)
*/
public ToolStateCollector(int totalTools, Map<String, KeyStrategy> keyStrategies) {
this.totalTools = totalTools;
this.keyStrategies = keyStrategies != null ? keyStrategies : Collections.emptyMap();
}
/**
* Creates an isolated update map for a tool at the given index. The returned map is a
* ConcurrentHashMap, allowing safe concurrent writes even if the tool implementation
* has internal async operations.
* @param index the tool index (0-based, in original toolCalls order)
* @return a new ConcurrentHashMap for the tool to write updates to
* @throws IllegalStateException if mergeAll() has already been called
*/
public Map<String, Object> createToolUpdateMap(int index) {
if (merged.get()) {
throw new IllegalStateException("Cannot create update map after mergeAll() has been called");
}
// Using ConcurrentHashMap to support tools with internal async operations
Map<String, Object> updateMap = new ConcurrentHashMap<>();
toolUpdatesByIndex.put(index, updateMap);
return updateMap;
}
/**
* Discards updates for a tool at the given index.
*
* <p>
* This is useful when a tool execution times out and we want to avoid merging partial
* updates that may still be written after the timeout.
* </p>
* @param index the tool index (0-based, in original toolCalls order)
*/
public void discardToolUpdateMap(int index) {
toolUpdatesByIndex.remove(index);View on GitHub (pinned to f82da0b50f)
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.
Example fix
// before Map<String,Object> m = collector.createToolUpdateMap(0); // after mergeAll() -> IllegalStateException // after Map<String,Object> m = collector.createToolUpdateMap(0); runTool(m); Map<String,Object> merged = collector.mergeAll();
Defensive patterns
Strategy: validation
Validate before calling
// before creating maps
if (collectorMerged(collector)) { throw new IllegalStateException("collector already merged"); } Try / catch
try { Map<String,Object> m = collector.createToolUpdateMap(i); } catch (IllegalStateException e) { /* recreate collector and re-run round */ } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- mergeAll() can only be called once
- Shell session not initialized. Call initialize() before exec
- Default Scheduled Agent Manager is shut down
- Schedule already started
- ConfigAgentWatcher is already started
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/c3f088784ece8574.
Report an issue: GitHub.