{"record":{"id":"6b967d292c138dc2","repo":"iflytek/astron-agent","slug":"skill-resource-download-returned-empty-body","errorCode":null,"errorMessage":"Skill resource download returned empty body","messagePattern":"Skill resource download returned empty body","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"warning","filePath":"console/backend/hub/src/main/java/com/iflytek/astron/console/hub/service/chat/springai/SkillRuntimeToolService.java","lineNumber":127,"sourceCode":"        validateResourceUrl(url);\n        Request request;\n        try {\n            request = new Request.Builder().url(url).get().build();\n        } catch (IllegalArgumentException exception) {\n            throw new IOException(\"Skill resource URL is not allowed\");\n        }\n        OkHttpClient client = httpClient.newBuilder()\n                .callTimeout(Duration.ofSeconds(30))\n                .followRedirects(false)\n                .followSslRedirects(false)\n                .build();\n        try (Response response = client.newCall(request).execute()) {\n            if (!response.isSuccessful()) {\n                throw new IOException(\"Skill resource download failed: HTTP \" + response.code());\n            }\n            ResponseBody body = response.body();\n            if (body == null) {\n                throw new IOException(\"Skill resource download returned empty body\");\n            }\n            return decodeText(readBounded(body, maxResourceBytes, \"Skill resource\"), body);\n        }\n    }\n\n    private byte[] readBounded(ResponseBody body, long limit, String description)\n            throws IOException {\n        validateLimit(limit, description);\n        long contentLength = body.contentLength();\n        if (contentLength > limit) {\n            throw new IOException(description + \" exceeds size limit\");\n        }\n        int readLimit = Math.toIntExact(limit + 1);\n        try (InputStream input = body.byteStream()) {\n            byte[] bytes = input.readNBytes(readLimit);\n            if (bytes.length > limit) {\n                throw new IOException(description + \" exceeds size limit\");\n            }","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/console/backend/hub/src/main/java/com/iflytek/astron/console/hub/service/chat/springai/SkillRuntimeToolService.java#L109-L145","documentation":"After a successful (2xx) response, downloadText checks that a ResponseBody exists; a 2xx response with a null body (possible with 204/205 or degenerate responses) is treated as a failed download and throws \"Skill resource download returned empty body\". Skill resources must carry actual text content, so an empty body is invalid.","triggerScenarios":"The resource endpoint returns 204 No Content, 205 Reset Content, or a 2xx with zero-length/absent body while downloadText reads a skill resource.","commonSituations":"The resource server (or an intermediate cache/proxy) stripped the body; uploading a zero-byte skill resource file; a misconfigured static file server returning 204 for missing files; a mock/stub server used in a test environment returning empty success responses.","solutions":["Verify the resource file was actually uploaded and is non-empty in the skill registry/storage.","Check proxy/CDN configuration that may strip or truncate response bodies.","Point the skill manifest at the correct resource URL — a 204 from a wrong path mimics an empty success.","Treat this as a publish-time data error: validate skill resources are non-empty when the skill is saved."],"exampleFix":"// before\n// zero-byte resource uploaded; downloadText throws at runtime\nstorageClient.put(bucket, key, new byte[0]);\n// after\nbyte[] data = Files.readAllBytes(path);\nif (data.length == 0) {\n    throw new IllegalArgumentException(\"Skill resource file is empty\");\n}\nstorageClient.put(bucket, key, data);","handlingStrategy":"try-catch","validationCode":"Response head = httpHead(url);\nboolean bodyExpected = head.code() == 200 && head.body() != null && head.headers().get(\"Content-Length\") != null;\nif (!bodyExpected) {\n    throw new IllegalStateException(\"Resource has no body\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    String content = service.downloadText(url, limit);\n} catch (IOException e) {\n    if (\"Skill resource download returned empty body\".equals(e.getMessage())) {\n        // treat as corrupt/missing resource; re-upload it\n    }\n}","preventionTips":["Reject zero-byte skill resource uploads at publish time.","Verify resource integrity (size/checksum) after uploading to storage.","Check proxies/caches for body-stripping configurations in test environments."],"tags":["http","empty-response","java","download"],"backgroundTag":"empty-response-body","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}