alibaba/spring-ai-alibaba · error · RuntimeException
Async tool execution failed
Error message
Async tool execution failed
What it means
AsyncToolCallback.call() awaits an async tool execution and, if the underlying future/completable completed exceptionally with a cause that is neither a RuntimeException nor an Error, it wraps the cause in a RuntimeException with the message "Async tool execution failed". This keeps checked/other Throwable causes propagatable in the synchronous caller context.
Source
Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tool/AsyncToolCallback.java:108
* @param context the tool execution context
* @return the result string
* @throws RuntimeException if the async operation fails
* @throws ToolCancelledException if the operation was cancelled
*/
@Override
default String call(String arguments, ToolContext context) {
try {
return callAsync(arguments, context).join();
}
catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException re) {
throw re;
}
if (cause instanceof Error err) {
throw err;
}
throw new RuntimeException("Async tool execution failed", cause);
}
catch (CancellationException e) {
throw new ToolCancelledException("Tool execution was cancelled", e);
}
}
/**
* Returns whether this tool executes asynchronously.
* @return true (always async for this interface)
*/
default boolean isAsync() {
return true;
}
/**
* Returns the timeout duration for this tool execution.
* @return the timeout duration, defaults to 5 minutes
*/View on GitHub (pinned to f82da0b50f)
Solutions
- Open the cause via getCause() to find the real failure — this message is only a wrapper.
- Throw RuntimeException subclasses from tool methods so they propagate unwrapped.
- Handle InterruptedException/IOException explicitly inside the tool and convert them to meaningful runtime exceptions.
- Catch this RuntimeException at the agent/tool-call boundary and map to a tool error result.
Example fix
// before
String result = tool.apply(input); // throws IOException
// after
String result;
try {
result = tool.apply(input);
} catch (IOException e) {
throw new UncheckedIOException("tool I/O failed", e);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (cause instanceof RuntimeException || cause instanceof Error) { /* will rethrow as-is; handle earlier */ } Type guard
if (t.getCause() instanceof RuntimeException re) { throw re; } Try / catch
try { result = asyncTool.call(input); } catch (RuntimeException e) { Throwable cause = e.getCause(); log.error("async tool failed", cause != null ? cause : e); } Prevention
- Throw unchecked exceptions from tool methods
- Convert checked exceptions to UncheckedIOException/IllegalStateException inside tools
- Always inspect getCause() when diagnosing
- Add tests for failing tools
When it happens
Trigger: A tool registered through the async callback interface throws a checked Throwable (e.g. IOException, InterruptedException) or its CompletableFuture completes exceptionally with a non-RuntimeException cause; call() unwraps the ExecutionException and re-wraps it.
Common situations: Tool methods declaring checked exceptions, I/O failures inside tools, executor/timeout failures surfaced as non-RuntimeException causes, custom tools that complete futures with Exception rather than RuntimeException.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
- Tool execution was cancelled
- TOOL_PARAMS_MISSING
- TOOL_EXECUTION_ERROR
- BUILD_TOOL_RESULT_ERROR
- WORKFLOW_EXECUTE_ERROR
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/069807052d2450ac.
Report an issue: GitHub.