{"record":{"id":"0dc99753de9d9263","repo":"alibaba/spring-ai-alibaba","slug":"retry-interrupted-0dc997","errorCode":null,"errorMessage":"Retry interrupted","messagePattern":"Retry interrupted","errorType":"exception","errorClass":"WebFetchException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tools/WebFetchTool.java","lineNumber":265,"sourceCode":"\t\t\t\t\tlogger.warn(\"Fetch attempt {} returned server error {} for URL: {}\", attempt + 1,\n\t\t\t\t\t\t\tresponse.statusCode(), url);\n\t\t\t\t\tattempt++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\tcatch (WebFetchException e) {\n\t\t\t\tlastException = e;\n\t\t\t\tif (e.getCause() instanceof InterruptedException) {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t\tlogger.warn(\"Fetch attempt {} failed for URL: {}: {}\", attempt + 1, url, e.getMessage());\n\t\t\t\tattempt++;\n\t\t\t}\n\t\t\tcatch (InterruptedException e) {\n\t\t\t\tThread.currentThread().interrupt();\n\t\t\t\tthrow new WebFetchException(\"Retry interrupted\", e);\n\t\t\t}\n\t\t}\n\n\t\tif (lastException == null) {\n\t\t\tthrow new WebFetchException(\"Failed after \" + (this.maxRetries + 1) + \" attempts\", null);\n\t\t}\n\t\telse if (lastException instanceof WebFetchException) {\n\t\t\tthrow new WebFetchException(\"Failed after \" + (this.maxRetries + 1) + \" attempts\", lastException);\n\t\t}\n\t\telse {\n\t\t\tthrow new WebFetchException(\n\t\t\t\t\t\"Failed after \" + (this.maxRetries + 1) + \" attempts: \" + lastException.getMessage(),\n\t\t\t\t\tlastException);\n\t\t}\n\t}\n\n\tprivate HttpResponse<String> fetchHtml(String url) {\n\t\tHttpRequest request = HttpRequest.newBuilder()","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tools/WebFetchTool.java#L247-L283","documentation":"WebFetchTool.fetchHtmlWithRetry wraps fetch failures in a WebFetchException. When the thread waiting between/inside fetch attempts is interrupted, the InterruptedException is caught, the interrupt flag is restored via Thread.currentThread().interrupt(), and this 'Retry interrupted' exception is thrown. It signals the retry loop was aborted by cancellation/shutdown rather than a network problem.","triggerScenarios":"Thread interrupt during WebFetchTool fetchHtmlWithRetry — e.g. task cancellation, executor shutdownNow, or a timeout mechanism interrupting the calling thread while the fetch/sleep between attempts is in progress.","commonSituations":"Calling the tool from a thread pool whose tasks get cancelled; Spring @Async with timeouts; shutting down an application while a web-fetch agent node is running; wrapping fetch in Future.get(timeout) which interrupts the worker.","solutions":["Let the interruption propagate: treat it as cancellation and stop the workflow rather than retrying or fetching again.","Check upstream code that calls Thread.interrupt() (Future.cancel(true), executor shutdownNow, timeouts) and extend its timeout if cancellation is unintentional.","Catch WebFetchException around the tool call and surface a clean cancellation message to the agent response.","Preserve the interrupt status in your own handler (re-interrupt) so outer layers see cancellation."],"exampleFix":"// before\nString html = webFetchTool.fetch(url);\n// after\ntry {\n    String html = webFetchTool.fetch(url);\n} catch (WebFetchException e) {\n    if (e.getCause() instanceof InterruptedException) {\n        Thread.currentThread().interrupt();\n        return \"fetch cancelled\";\n    }\n    throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    String html = webFetchTool.fetch(url);\n} catch (WebFetchException e) {\n    if (e.getCause() instanceof InterruptedException) {\n        Thread.currentThread().interrupt();\n        return Optional.empty(); // treat as cancelled\n    }\n    throw e;\n}","preventionTips":["Avoid interrupting worker threads mid-fetch; prefer cooperative cancellation points between nodes","Increase deadlines in code that cancels fetches via Future.cancel(true)","Check Thread.currentThread().isInterrupted() before starting long fetch chains"],"tags":["http","interruption","retry"],"backgroundTag":"request-timeout","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"}