{"record":{"id":"655f97b42e7a6f6f","repo":"signalapp/Signal-Server","slug":"response-body-was-below-minimum-d-d","errorCode":null,"errorMessage":"Response body was below minimum: %d, %d","messagePattern":"Response body was below minimum: (.+?), (.+?)","errorType":"console","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/workers/CopyToS3Command.java","lineNumber":94,"sourceCode":"      final String filename,\n      final int minimumExpectedSize) throws IOException, InterruptedException {\n\n    final HttpRequest httpRequest = HttpRequest.newBuilder()\n        .uri(sourceUri)\n        .timeout(Duration.ofMinutes(1))\n        .GET()\n        .build();\n\n    try (final HttpClient httpClient = HttpClient.newBuilder()\n        .followRedirects(HttpClient.Redirect.NORMAL)\n        .build();\n        final S3Client s3Client = S3Client.builder().build()) {\n\n      final HttpResponse<byte[]> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());\n\n      if (httpResponse.statusCode() == 200) {\n        if (httpResponse.body().length < minimumExpectedSize) {\n          throw new IOException(\"Response body was below minimum: %d, %d\"\n              .formatted(httpResponse.body().length, minimumExpectedSize));\n        }\n\n        final String contentType = httpResponse.headers().firstValue(\"Content-Type\").orElse(\"application/octet-stream\");\n\n        s3Client.putObject(PutObjectRequest.builder()\n            .bucket(s3Bucket)\n            .key(filename)\n            .contentType(contentType)\n            .contentLength((long) httpResponse.body().length)\n            .build(), RequestBody.fromBytes(httpResponse.body()));\n\n        logger.info(\"Copied {} bytes from {} to s3://{}/{}\",\n            httpResponse.body().length,\n            httpRequest.uri(),\n            s3Bucket,\n            filename);\n      } else {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/workers/CopyToS3Command.java#L76-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the source URI actually serves the expected file (curl it and check size)","Lower --minimum-expected-size to match the real content size if it was set too high","Check for proxies/CDN returning a 200 stub page instead of the file"],"exampleFix":"// before\ncopy-to-s3 --source https://example.org/data --minimum-expected-size 10000000\n// after\ncopy-to-s3 --source https://example.org/data --minimum-expected-size 1024","handlingStrategy":"validation","validationCode":"HttpResponse<byte[]> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofByteArray());\nif (resp.statusCode() != 200 || resp.body().length < minimumExpectedSize) {\n    throw new IOException(\"bad source response: \" + resp.statusCode() + \", \" + resp.body().length);\n}","typeGuard":null,"tryCatchPattern":"try { copyToS3(...); } catch (IOException e) { logger.error(\"copy failed: {}\", e.getMessage()); }","preventionTips":["curl the source URI and check content-length before running","Set minimumExpectedSize conservatively below the true size","Watch for proxies returning small 200 stub pages"],"tags":["network","io","cli"],"backgroundTag":"empty-response-body","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}