pypa/pip · error · IncompleteDownloadError
incomplete-download
incomplete-download
Error message
Download failed because not enough bytes were received ({download_status}) What it means
Raised as IncompleteDownloadError inside _attempt_resumes_or_redownloads when, during a resume request, the server's 206 'Content-Range' header start byte does not equal the bytes already received. The server is misbehaving (returning a range at an unexpected offset), so pip aborts rather than write a corrupted file. Distinct from the exhaustion case: this fires on the first mismatched resume before retries run out.
Solutions
- Retry the install; transient mid-stream mismatches often clear on a fresh download.
- Disable resume retries for this mirror (--resume-retries 0) so pip restarts cleanly instead of resuming.
- Switch to an index/mirror that honours HTTP Range requests correctly (e.g. PyPI directly).
- Clear the pip cache (pip cache purge) in case a stale partial is being reused.
Defensive patterns
Strategy: retry
Try / catch
from pip._internal.exceptions import IncompleteDownloadError
for _ in range(3):
try:
run_pip_install(["pkg"])
break
except IncompleteDownloadError:
continue Prevention
- Use mirrors/indexes that honour HTTP Range and Content-Range correctly.
- Clear the pip cache if a partial seems to be reused.
- Avoid mid-file resume against misbehaving CDNs; use --resume-retries 0 there.
When it happens
Trigger: A resumable download is retried (--resume-retries>0); the server returns 206 Partial Content whose Content-Range 'bytes START-...' START differs from download.bytes_received (e.g. server restarted from 0, or rewrote the range).
Common situations: Misconfigured CDN or caching proxy that ignores or rewrites Range/Content-Range; mirrors that don't honour byte ranges; load balancers shuffling resume requests to a different backend with a different file.
Related errors
- Could not install requirement
- Cannot determine archive format of
- connection-failed
- connection-timeout
- Error reading pylock file
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/a9bff1440165fbed.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/network/download.py:279
try:
resume_resp = self._http_get_resume(download, should_match=first_resp)
# Fallback: if the server responded with 200 (i.e., the file has
# since been modified or range requests are unsupported) or any
# other unexpected status, restart the download from the beginning.
must_restart = resume_resp.status_code != HTTPStatus.PARTIAL_CONTENT
if must_restart:
download.reset_file()
download.size = _get_http_response_size(resume_resp)
first_resp = resume_resp
else:
# If the resume request starts at the wrong location, fail
# outright since the server is misbehaving.
content_range = resume_resp.headers.get("Content-Range", "")
resumed_at = content_range.lower().partition("bytes ")[2]
resumed_at = resumed_at.partition("-")[0]
if resumed_at and resumed_at != str(download.bytes_received):
raise IncompleteDownloadError(download)
self._process_response(download, resume_resp)
except (
ConnectionFailedError,
ConnectionTimeoutError,
ProxyConnectionError,
SSLVerificationError,
ReadTimeoutError,
ProtocolError,
OSError,
):
# The error handling here is tricky, a few notes:
#
# - The diagnostic connection errors are raised by our custom
# requests.request() connection exception handler.
# - ProtocolError is raised by urllib3 when the returned data length
# doesn't match the response Content-Length.
# - ReadTimeoutError / ProtocolError come straight from urllib3 (viaView on GitHub (pinned to f399c37189)