signalapp/Signal-Server · error · IOException

Response body was below minimum

Error message

Response body was below minimum: %d, %d

What it means

CopyToS3Command validates that the HTTP response fetched from the source URI is at least `minimumExpectedSize` bytes before uploading to S3. A 200 response with a body smaller than that threshold indicates a truncated or unexpectedly tiny payload, so an IOException is thrown instead of storing bad data.

Solutions

  1. Verify the source URI actually serves the expected file (curl it and check size)
  2. Lower --minimum-expected-size to match the real content size if it was set too high
  3. Check for proxies/CDN returning a 200 stub page instead of the file

Example fix

// before
copy-to-s3 --source https://example.org/data --minimum-expected-size 10000000
// after
copy-to-s3 --source https://example.org/data --minimum-expected-size 1024
Defensive patterns

Strategy: validation

Validate before calling

HttpResponse<byte[]> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofByteArray());
if (resp.statusCode() != 200 || resp.body().length < minimumExpectedSize) {
    throw new IOException("bad source response: " + resp.statusCode() + ", " + resp.body().length);
}

Try / catch

try { copyToS3(...); } catch (IOException e) { logger.error("copy failed: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Running the `copy-to-s3` command with `--minimum-expected-size` (default or explicit) larger than the actual byte length of the response body returned with HTTP 200 from the source URI.

Common situations: Source serves a redirect/empty stub page with 200 instead of the file; wrong --minimum-expected-size value; upstream changed the resource; proxy intercepting the request and returning a small HTML page.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/655f97b42e7a6f6f. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/workers/CopyToS3Command.java:94

      final String filename,
      final int minimumExpectedSize) throws IOException, InterruptedException {

    final HttpRequest httpRequest = HttpRequest.newBuilder()
        .uri(sourceUri)
        .timeout(Duration.ofMinutes(1))
        .GET()
        .build();

    try (final HttpClient httpClient = HttpClient.newBuilder()
        .followRedirects(HttpClient.Redirect.NORMAL)
        .build();
        final S3Client s3Client = S3Client.builder().build()) {

      final HttpResponse<byte[]> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());

      if (httpResponse.statusCode() == 200) {
        if (httpResponse.body().length < minimumExpectedSize) {
          throw new IOException("Response body was below minimum: %d, %d"
              .formatted(httpResponse.body().length, minimumExpectedSize));
        }

        final String contentType = httpResponse.headers().firstValue("Content-Type").orElse("application/octet-stream");

        s3Client.putObject(PutObjectRequest.builder()
            .bucket(s3Bucket)
            .key(filename)
            .contentType(contentType)
            .contentLength((long) httpResponse.body().length)
            .build(), RequestBody.fromBytes(httpResponse.body()));

        logger.info("Copied {} bytes from {} to s3://{}/{}",
            httpResponse.body().length,
            httpRequest.uri(),
            s3Bucket,
            filename);
      } else {

View on GitHub (pinned to 100ab61c82)