yt-dlp/yt-dlp · error · IncompleteRead

{partial} bytes read, {expected} more expected

Error message

{partial} bytes read, {expected} more expected

What it means

IncompleteRead (a TransportError) raised by the requests handler: urllib3 raised ProtocolError wrapping an http.client.IncompleteRead, meaning the server closed the connection before the full body arrived even though Content-Length promised more bytes. The message reports .partial = bytes actually read and .expected = bytes still owed; the original urllib3 exception is chained as __cause__.

Source

Thrown at yt_dlp/networking/_requests.py:152

            data = self._real_read(amt)
            if self.fp.closed:
                self.close()
            return data
        # See urllib3.response.HTTPResponse.read() for exceptions raised on read
        except urllib3.exceptions.SSLError as e:
            raise SSLError(cause=e) from e

        except urllib3.exceptions.ProtocolError as e:
            # IncompleteRead is always contained within ProtocolError
            # See urllib3.response.HTTPResponse._error_catcher()
            ir_err = next(
                (err for err in (e.__context__, e.__cause__, *variadic(e.args))
                 if isinstance(err, http.client.IncompleteRead)), None)
            if ir_err is not None:
                # `urllib3.exceptions.IncompleteRead` is subclass of `http.client.IncompleteRead`
                # but uses an `int` for its `partial` property.
                partial = ir_err.partial if isinstance(ir_err.partial, int) else len(ir_err.partial)
                raise IncompleteRead(partial=partial, expected=ir_err.expected) from e
            raise TransportError(cause=e) from e

        except urllib3.exceptions.HTTPError as e:
            # catch-all for any other urllib3 response exceptions
            raise TransportError(cause=e) from e


class RequestsHTTPAdapter(requests.adapters.HTTPAdapter):
    def __init__(self, ssl_context=None, proxy_ssl_context=None, source_address=None, **kwargs):
        self._pm_args = {}
        if ssl_context:
            self._pm_args['ssl_context'] = ssl_context
        if source_address:
            self._pm_args['source_address'] = (source_address, 0)
        self._proxy_ssl_context = proxy_ssl_context or ssl_context
        super().__init__(**kwargs)

    def init_poolmanager(self, *args, **kwargs):

View on GitHub (pinned to 81ecd58b13)

Solutions

  1. Retry the request - yt-dlp retries fragments by default; raise --retries/--fragment-retries if it persists
  2. Resume instead of restarting: rely on Range requests (yt-dlp does this for fragmented formats) so only the missing tail is refetched
  3. Bypass the truncating middlebox: different network, disable VPN/proxy, or install the requests handler as an alternative path
  4. If truncation is byte-stable and reproducible, the server is misreporting length - confirm with curl -C - and report the extractor/URL
Defensive patterns

Strategy: retry

Try / catch

import time
from yt_dlp.networking.exceptions import IncompleteRead

start = 0
for attempt in range(5):
    try:
        return fetch(url, start_byte=start)
    except IncompleteRead as e:
        start = e.partial or start  # resume from what already arrived
        time.sleep(1.5 ** attempt)
raise TimeoutError('server keeps truncating the body')

Prevention

When it happens

Trigger: Server or an intermediary (proxy/CDN) truncates the response body; connection dropped mid-download; misranged requests where the server sends fewer bytes than declared; flaky mobile or VPN links during large downloads.

Common situations: Media downloads cut off by idle-connection kills; rate-limiting middleboxes closing streams early; HTTP/1.1 keep-alive races; servers with wrong Content-Length.

Related errors


AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22). Data as JSON: /api/errors/054d3205f58ae339. Report an issue: GitHub.