{"record":{"id":"168c75034fbcd592","repo":"alibaba/spring-ai-alibaba","slug":"tool-failed-attempt-retrying-in-ms","errorCode":null,"errorMessage":"Tool '{}' failed (attempt {}/{}), retrying in {}ms: {}","messagePattern":"Tool '(.+?)' failed \\(attempt (.+?)/(.+?)\\), retrying in (.+?)ms: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/toolretry/ToolRetryInterceptor.java","lineNumber":112,"sourceCode":"\t\t\t\tthrow new RuntimeException(result);\n\t\t\t}\n\t\t\tcatch (Exception e) {\n\t\t\t\tlastException = e;\n\n\t\t\t\t// Check if we should retry this exception\n\t\t\t\tif (!retryOn.test(e)) {\n\t\t\t\t\tlog.debug(\"Exception {} not configured for retry, re-throwing\", e.getClass().getSimpleName());\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\n\t\t\t\t// Last attempt failed: Stop the operation\n\t\t\t\tif (attempt >= maxAttempts - 1) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\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","sourceCodeStart":94,"sourceCodeEnd":130,"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#L94-L130","documentation":"ToolRetryInterceptor logs this warning each time a tool call fails but is still eligible for another attempt. It reports the tool name, attempt count out of maxAttempts, the computed backoff delay, and the failure message. Tool execution continues after the sleep; only non-retryable exceptions or exhausted attempts terminate the loop.","triggerScenarios":"interceptToolCall catches an exception from the underlying ToolCallback, attempt < maxAttempts - 1, and the exception passes the retryable check; the warning fires before Thread.sleep(delay) for each failed intermediate attempt.","commonSituations":"1) A tool invoking a flaky HTTP endpoint that intermittently times out. 2) A database-backed tool hitting transient connection pool exhaustion. 3) A shell/MCP tool crashing intermittently under load. 4) maxAttempts set high so users see this warning repeatedly while an endpoint is down.","solutions":["Read the exception message/detail in the warning to fix the underlying tool failure (endpoint down, timeout too low, dependency missing).","Tune maxAttempts and the backoff (calculateDelay) so retries stop earlier for persistently failing tools.","Configure a non-retryable exception predicate so deterministic failures (bad tool arguments, permission errors) rethrow immediately instead of retrying.","Add resilience inside the tool itself (timeouts, circuit breaker) rather than relying on interceptor-level retries."],"exampleFix":"// before\nnew ToolRetryInterceptor(5) // retries everything 5 times\n// after\nnew ToolRetryInterceptor(3)\n    .retryableExceptionPredicate(e -> e instanceof java.io.IOException || e instanceof java.util.concurrent.TimeoutException)","handlingStrategy":"retry","validationCode":"if (maxAttempts < 1 || maxAttempts > 10) {\n    throw new IllegalArgumentException(\"maxAttempts must be between 1 and 10\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    return interceptToolCall(req, handler);\n} catch (RuntimeException e) {\n    log.warn(\"Tool {} exhausted retries: {}\", req.getToolName(), e.getMessage());\n    return ToolCallResponse.builder().status(\"error\").content(\"Tool unavailable\").build();\n}","preventionTips":["Bound maxAttempts and backoff delay explicitly","Classify exceptions: retry only transient ones","Add timeouts inside the tool itself","Alert on repeated retry warnings for the same tool"],"tags":["retry","tool-call","backoff","interceptor"],"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"}