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
- Retry the request - yt-dlp retries fragments by default; raise --retries/--fragment-retries if it persists
- Resume instead of restarting: rely on Range requests (yt-dlp does this for fragmented formats) so only the missing tail is refetched
- Bypass the truncating middlebox: different network, disable VPN/proxy, or install the requests handler as an alternative path
- 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
- Enable generous --retries and --fragment-retries on unreliable links
- Prefer resumable/ranged downloads so a truncated read is cheap to recover from
- Log partial/expected - an identical truncation every time points at the server, not your network
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
- {partial} bytes read, {expected} more expected
- HTTP Error {status}: {reason}
- HTTP Error {status}: {reason}
- Failed to import "requests" request handler: {e}
- Failed to patch _PERCENT_RE in urllib3 (does the attribute e
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/054d3205f58ae339.
Report an issue: GitHub.