{"record":{"id":"346bec415c41d435","repo":"alibaba/spring-ai-alibaba","slug":"tool-call-failed-after-maxattempts-attempts","errorCode":null,"errorMessage":"Tool call failed after ${maxAttempts} attempts","messagePattern":"Tool call failed after (.+?) attempts","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/toolretry/ToolRetryInterceptor.java","lineNumber":127,"sourceCode":"\n\t\t\t\t// Calculate delay\n\t\t\t\tlong delay = calculateDelay(attempt);\n\t\t\t\tlog.warn(\"Tool '{}' failed (attempt {}/{}), retrying in {}ms: {}\",\n\t\t\t\t\t\ttoolName, attempt + 1, maxAttempts, delay, e.getMessage());\n\n\t\t\t\ttry {\n\t\t\t\t\tThread.sleep(delay);\n\t\t\t\t}\n\t\t\t\tcatch (InterruptedException ie) {\n\t\t\t\t\tThread.currentThread().interrupt();\n\t\t\t\t\tthrow new RuntimeException(\"Retry interrupted\", ie);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// All retries exhausted\n\t\tif (onFailure == OnFailureBehavior.RAISE) {\n\t\t\tthrow new RuntimeException(\"Tool call failed after \" + maxAttempts + \" attempts\", lastException);\n\t\t}\n\t\telse {\n\t\t\t// Return error message as tool response\n\t\t\tString errorMessage = errorFormatter != null\n\t\t\t\t\t? errorFormatter.apply(lastException)\n\t\t\t\t\t: \"Tool call failed after \" + maxAttempts + \" attempts: \" + lastException.getMessage();\n\n\t\t\tlog.error(\"Tool '{}' failed after {} attempts: {}\", toolName, maxAttempts, lastException.getMessage());\n\t\t\treturn ToolCallResponse.of(request.getToolCallId(), request.getToolName(), errorMessage);\n\t\t}\n\t}\n\n\tprivate long calculateDelay(int retryNumber) {\n\t\tlong delay = (long) (initialDelayMs * Math.pow(backoffFactor, retryNumber));\n\t\tdelay = Math.min(delay, maxDelayMs);\n\n\t\tif (jitter) {\n\t\t\t// Add random jitter ±25%","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/toolretry/ToolRetryInterceptor.java#L109-L145","documentation":"When all maxAttempts attempts fail and OnFailureBehavior is RAISE, ToolRetryInterceptor throws RuntimeException(\"Tool call failed after N attempts\", lastException), preserving the final underlying exception as the cause. This signals retry exhaustion rather than a single transient failure.","triggerScenarios":"A tool call throws (or returns non-success status) on every one of the maxAttempts attempts with retryOn matching each failure, and onFailure behavior is RAISE.","commonSituations":"Downstream service permanently down; invalid tool arguments failing deterministically; maxAttempts too low for a flaky dependency; retryOn predicate matching exceptions that will never succeed.","solutions":["Inspect the cause (lastException) for the root failure and fix it","Increase maxAttempts or backoff delay for slow-recovering dependencies","Narrow retryOn to retry only transient exceptions","Switch onFailure to return the error message as a tool response instead of raising"],"exampleFix":"// before\nToolRetryInterceptor.builder().maxAttempts(2).build();\n// after\nToolRetryInterceptor.builder().maxAttempts(5).backoffMultiplier(2.0).build();","handlingStrategy":"try-catch","validationCode":"// pre-check the dependency is reachable before expensive retry loops\n// and size attempts for expected recovery time\nToolRetryInterceptor.builder().maxAttempts(5).backoffMultiplier(2.0).retryOn(IOException.class::isInstance).build();","typeGuard":null,"tryCatchPattern":"try { return interceptor.interceptToolCall(request, handler); } catch (RuntimeException e) { log.error(\"Tool {} failed after retries\", request.getName(), e.getCause() != null ? e.getCause() : e); return ToolCallResponse.of(\"Tool temporarily unavailable\"); }","preventionTips":["Always inspect the cause chain for the root error","Tune maxAttempts/backoff to the dependency's recovery profile","Retry only transient exceptions via retryOn","Use OnFailureBehavior returning error messages for non-critical tools"],"tags":["retry","exhaustion","tool-call"],"backgroundTag":"api-request-failed","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"}