signalapp/Signal-Server · error · IOException

Got a non-200 reply from source URI:

Error message

Got a non-200 reply from source URI: 

What it means

CopyToS3Command only uploads when the source server returns HTTP 200. Any other status code (404, 403, 500, redirects, etc.) causes an IOException with this message, including the actual status code, because the command does not follow non-200 flows.

Solutions

  1. Verify the source URI with curl -I and confirm it returns 200
  2. Fix authentication or credentials if the source returns 401/403
  3. Correct the URI/path if it returns 404
  4. Ensure the server responds 200 rather than a redirect, or point at the final URL

Example fix

// before
httpRequest to https://example.org/old-path
// after
httpRequest to https://example.org/new-path (verified 200 via curl -I)
Defensive patterns

Strategy: validation

Validate before calling

HttpRequest request = HttpRequest.newBuilder(URI.create(sourceUri)).build();
HttpResponse<Void> probe = httpClient.send(request, HttpResponse.BodyHandlers.discarding());
if (probe.statusCode() != 200) throw new IllegalStateException("source not 200: " + probe.statusCode());

Try / catch

try { copyToS3(...); } catch (IOException e) { logger.error("non-200 from {}: {}", sourceUri, e.getMessage()); }

Prevention

When it happens

Trigger: Running `copy-to-s3` against a URI that responds with any status other than 200 — the httpClient.send() completes but the else-branch throws.

Common situations: Typo in the source URI path; missing auth so the origin replies 403; server moved the file (404); source replying 301/302 redirect that isn't followed; upstream 5xx during maintenance.

Related errors


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

Appendix: source

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

              .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 {
        throw new IOException("Got a non-200 reply from source URI: " + httpResponse.statusCode());
      }
    }
  }
}

View on GitHub (pinned to 100ab61c82)