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 a generic exception that is neither a TimeoutException nor an OverflowException; if the underlying message is empty the default text 'Failed while transferring the response body' is used. It is wrapped as a ResponseTransferException carrying both request and response.

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 9b200fc580)

Solutions

  1. Catch ResponseTransferException and retry with backoff (transient resets often succeed).
  2. If using a 'sink' file, verify the destination is writable and has free disk space.
  3. Check the underlying exception (getPrevious()) for the real OS-level error and address it.
  4. For flaky endpoints, enable retry middleware or an idempotent retry policy.

Example fix

// before
$resp = $client->get($largeFileUrl); // fails mid-transfer
// after
try {
    $resp = $client->get($largeFileUrl);
} catch (ResponseTransferException $e) {
    // log $e->getPrevious() and retry with backoff
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the sink destination is writable before the transfer.
if (is_string($options['sink'] ?? null) && !is_writable(dirname($options['sink']))) {
    throw new \RuntimeException('sink directory is not writable');
}

Try / catch

try {
    $response = $client->get($url);
} catch (\GuzzleHttp\Exception\ResponseTransferException $e) {
    // inspect $e->getPrevious() for the OS-level cause; retry with backoff if transient
}

Prevention

When it happens

Trigger: The transport read fails partway through (broken pipe, connection reset, SSL read error) without matching a recognized timeout signature; a custom sink stream throws on write; the underlying resource is closed externally.

Common situations: Unstable networks dropping connections mid-download; intermediate proxies resetting the connection; disk-full or permission errors writing to a 'sink' file; resource exhaustion.

Related errors


AI-assisted analysis of guzzle/guzzle@9b200fc580 (2026-08-04). Data as JSON: /data/errors/71c467f405249553.json. Report an issue: GitHub.