iflytek/astron-agent · warning · IOException

Skill resource download returned empty body

Error message

Skill resource download returned empty body

What it means

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.

Solutions

  1. Verify the resource file was actually uploaded and is non-empty in the skill registry/storage.
  2. Check proxy/CDN configuration that may strip or truncate response bodies.
  3. Point the skill manifest at the correct resource URL — a 204 from a wrong path mimics an empty success.
  4. Treat this as a publish-time data error: validate skill resources are non-empty when the skill is saved.

Example fix

// before
// zero-byte resource uploaded; downloadText throws at runtime
storageClient.put(bucket, key, new byte[0]);
// after
byte[] data = Files.readAllBytes(path);
if (data.length == 0) {
    throw new IllegalArgumentException("Skill resource file is empty");
}
storageClient.put(bucket, key, data);
Defensive patterns

Strategy: try-catch

Validate before calling

Response head = httpHead(url);
boolean bodyExpected = head.code() == 200 && head.body() != null && head.headers().get("Content-Length") != null;
if (!bodyExpected) {
    throw new IllegalStateException("Resource has no body");
}

Try / catch

try {
    String content = service.downloadText(url, limit);
} catch (IOException e) {
    if ("Skill resource download returned empty body".equals(e.getMessage())) {
        // treat as corrupt/missing resource; re-upload it
    }
}

Prevention

When it happens

Trigger: The resource endpoint returns 204 No Content, 205 Reset Content, or a 2xx with zero-length/absent body while downloadText reads a skill resource.

Common situations: 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.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/6b967d292c138dc2. Report an issue: GitHub.

Appendix: source

Thrown at console/backend/hub/src/main/java/com/iflytek/astron/console/hub/service/chat/springai/SkillRuntimeToolService.java:127

        validateResourceUrl(url);
        Request request;
        try {
            request = new Request.Builder().url(url).get().build();
        } catch (IllegalArgumentException exception) {
            throw new IOException("Skill resource URL is not allowed");
        }
        OkHttpClient client = httpClient.newBuilder()
                .callTimeout(Duration.ofSeconds(30))
                .followRedirects(false)
                .followSslRedirects(false)
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Skill resource download failed: HTTP " + response.code());
            }
            ResponseBody body = response.body();
            if (body == null) {
                throw new IOException("Skill resource download returned empty body");
            }
            return decodeText(readBounded(body, maxResourceBytes, "Skill resource"), body);
        }
    }

    private byte[] readBounded(ResponseBody body, long limit, String description)
            throws IOException {
        validateLimit(limit, description);
        long contentLength = body.contentLength();
        if (contentLength > limit) {
            throw new IOException(description + " exceeds size limit");
        }
        int readLimit = Math.toIntExact(limit + 1);
        try (InputStream input = body.byteStream()) {
            byte[] bytes = input.readNBytes(readLimit);
            if (bytes.length > limit) {
                throw new IOException(description + " exceeds size limit");
            }

View on GitHub (pinned to 5e758547a8)