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
- 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
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
- 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
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
- 503 Service Unavailable
- Got a non-200 reply from source URI:
- Registration service failure
- registration service unavailable
- Key ID %08x has been reserved or revoked and may not be…
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)