guzzle/guzzle · error · GuzzleHttp\Exception\ResponseTransferException
Failed while transferring the response body
Error message
Failed while transferring the response body
What it means
Thrown by StreamHandler::drain() when copying the response body fails with any Exception that is not a ResponseException, TimeoutException, or OverflowException (those are handled earlier). It is wrapped as a ResponseTransferException using the original $e->getMessage() when non-empty, else the fallback 'Failed while transferring the response body'. It carries the request, response, and the underlying exception.
Solutions
- Inspect the previous exception ($e->getPrevious()) for the real transport error and address that (TLS, connectivity, corrupt encoding).
- Retry idempotent requests; the response is partial and unusable.
- For large bodies, stream to disk and enable retry with Range/offset to resume.
Example fix
// before
try { $client->get($u); }
catch (\GuzzleHttp\Exception\ResponseTransferException $e) { /* generic, lose detail */ }
// after
try { $client->get($u); }
catch (\GuzzleHttp\Exception\ResponseTransferException $e) {
$cause = $e->getPrevious() ? $e->getPrevious()->getMessage() : $e->getMessage();
$logger->error('body transfer failed: '.$cause); // act on the real cause
} Defensive patterns
Strategy: retry
Try / catch
try {
$resp = $client->get($u);
} catch (\GuzzleHttp\Exception\ResponseTransferException $e) {
$cause = $e->getPrevious();
// only retry idempotent methods; log the underlying cause for diagnosis
if (in_array($method, ['GET','HEAD','PUT','DELETE'], true)) {
$resp = $client->get($u);
}
} Prevention
- Inspect getPrevious() to find the real transport error before retrying.
- Retry only idempotent requests on mid-body transfer failures.
- Stream large bodies to a sink so partial progress can be resumed with Range.
When it happens
Trigger: An I/O failure mid-body that is not a timeout: the transport stream read() throws a RuntimeException (e.g. SSL connection reset not matched to the network-error list, broken pipe, filter error), or a dechunk/inflate filter raises. The generic catch is at src/Handler/StreamHandler.php:707.
Common situations: Connection reset by peer while reading the body; SSL errors during body transfer; zlib inflate failure on a corrupt gzip body; broken pipe from an intermediary proxy; intermittent network errors.
Related errors
- Failed to write the response body
- Response body ended before the declared Content-Length was…
- Timed out while transferring the response body
- Unable to write to stream
- $e->getMessage()
AI-assisted analysis of guzzle/guzzle@d1cbca7697 (2026-08-06).
Data as JSON: /api/errors/71c467f405249553.
Report an issue: GitHub.
Appendix: source
Thrown at src/Handler/StreamHandler.php:710
// that number of bytes has been read. This can prevent infinitely
// reading from a stream when dealing with servers that do not
// honor Connection: Close headers.
$copied = Psr7\Utils::copyToStream($source, $target, $copyLimit);
} catch (ResponseException $e) {
throw $e;
} catch (TimeoutException $e) {
throw new ResponseTimeoutException(
'Timed out while transferring the response body',
$request,
$response,
$e
);
} catch (\OverflowException $e) {
throw new ResponseException($e->getMessage(), $request, $response, $e);
} catch (\Exception $e) {
// Any other response-body transfer failure surfaces as a
// ResponseTransferException carrying the response.
throw new ResponseTransferException(
$e->getMessage() !== '' ? $e->getMessage() : 'Failed while transferring the response body',
$request,
$response,
$e
);
}
$receivedLength = $encodedBody !== null ? $encodedBody->getBytesRead() : $copied;
if ($declaredLength !== null && $receivedLength < $declaredLength) {
throw new ResponseTransferException(
'Response body ended before the declared Content-Length was reached',
$request,
$response
);
}
try {
if ($sink->isSeekable()) {View on GitHub (pinned to d1cbca7697)