{"record":{"id":"e1ec260d1a419c29","repo":"alibaba/spring-ai-alibaba","slug":"request-was-interrupted","errorCode":null,"errorMessage":"Request was interrupted","messagePattern":"Request was 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":346,"sourceCode":"\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic URI uri() {\n\t\t\t\t\treturn byteResponse.uri();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic java.net.http.HttpClient.Version version() {\n\t\t\t\t\treturn byteResponse.version();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new WebFetchException(\"Network error while fetching URL: \" + e.getMessage(), e);\n\t\t}\n\t\tcatch (InterruptedException e) {\n\t\t\tThread.currentThread().interrupt();\n\t\t\tthrow new WebFetchException(\"Request was interrupted\", e);\n\t\t}\n\t}\n\n\tprivate Optional<Charset> extractCharset(HttpResponse<?> response) {\n\t\treturn response.headers()\n\t\t\t.firstValue(\"Content-Type\")\n\t\t\t.flatMap(contentType -> {\n\t\t\t\tMatcher matcher = CHARSET_PATTERN.matcher(contentType);\n\t\t\t\tif (matcher.find()) {\n\t\t\t\t\tString charsetName = matcher.group(1);\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn Optional.of(Charset.forName(charsetName));\n\t\t\t\t\t}\n\t\t\t\t\tcatch (Exception e) {\n\t\t\t\t\t\tlogger.warn(\"Unsupported charset '{}', falling back to UTF-8\", charsetName);\n\t\t\t\t\t\treturn Optional.empty();\n\t\t\t\t\t}\n\t\t\t\t}","sourceCodeStart":328,"sourceCodeEnd":364,"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#L328-L364","documentation":"fetchHtml catches InterruptedException from HttpClient.send, restores the interrupt flag, and throws WebFetchException('Request was interrupted'). It means the HTTP request was cancelled by a thread interrupt, not by a network failure.","triggerScenarios":"Thread interrupt while the blocking HttpClient.send call is in flight — task cancellation, executor shutdownNow, or Future.cancel(true) from a caller imposing a deadline.","commonSituations":"Agent graph node cancelled by the orchestrator; application shutdown during a fetch; timeouts implemented via interrupting worker threads.","solutions":["Treat it as cancellation: stop processing and re-check the interrupt flag in your own code.","Locate and adjust the interrupting code (increase timeout, avoid shutdown during in-flight requests).","Catch WebFetchException, check for InterruptedException cause, and return a cancellation status.","Use non-blocking timeouts at a higher level instead of interrupting mid-request where possible."],"exampleFix":"// before\nString html = tool.fetch(url);\n// after\ntry {\n    String html = tool.fetch(url);\n} catch (WebFetchException e) {\n    if (e.getCause() instanceof InterruptedException) {\n        Thread.currentThread().interrupt();\n        throw new TaskCancelledException(url);\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 null; // cancelled, do not retry\n    }\n    throw e;\n}","preventionTips":["Don't implement timeouts by interrupting fetch threads; use HttpClient's own timeouts","Gracefully drain in-flight fetches before executor shutdown","Track cancellation tokens in your workflow instead of raw interrupts"],"tags":["http","interruption"],"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"}