{"record":{"id":"9fd99327947efac0","repo":"invoke-ai/InvokeAI","slug":"resume-refused-by-server-restart-required","errorCode":null,"errorMessage":"Resume refused by server. Restart required.","messagePattern":"Resume refused by server\\. Restart required\\.","errorType":"exception","errorClass":"DownloadJobCancelledException","httpStatus":null,"severity":"warning","filePath":"invokeai/app/services/download/download_default.py","lineNumber":484,"sourceCode":"            # Range not satisfiable - local partial is already complete\n            match = re.fullmatch(r\"bytes \\*/(\\d+)\", resp.headers.get(\"Content-Range\", \"\"), flags=re.IGNORECASE)\n            # Content-Range is optional on 416 responses. Reuse the size known when\n            # the download started, but never resume_from itself: that would accept\n            # every partial file as complete.\n            expected = int(match.group(1)) if match else (job.expected_total_bytes or job.total_bytes or None)\n            if expected is not None and resume_from == expected:\n                job.total_bytes = expected\n                job.expected_total_bytes = expected\n                job.bytes = resume_from\n                job.download_path = job.download_path or job.dest\n                self._in_progress_path(job.download_path).rename(job.download_path)\n                self._signal_job_started(job)\n                self._signal_job_complete(job)\n                return\n            job.resume_required = True\n            job.resume_message = \"Resume refused by server. Restart required.\"\n            job.pause()\n            raise DownloadJobCancelledException(\"Resume refused by server. Restart required.\")\n        if not resp.ok:\n            host = urlparse(str(resp.url or url)).netloc\n            status = resp.status_code\n            reason = resp.reason\n            if status >= 500:\n                self._logger.error(f\"Remote server error from {host}: HTTP {status} {reason}\")\n                raise HTTPError(reason)\n            self._logger.error(f\"Download failed from {host}: HTTP {status} {reason}\")\n            raise HTTPError(reason)\n\n        job.content_type = resp.headers.get(\"Content-Type\")\n        job.etag = resp.headers.get(\"ETag\") or job.etag\n        job.last_modified = resp.headers.get(\"Last-Modified\") or job.last_modified\n        content_length = int(resp.headers.get(\"content-length\", 0))\n\n        if job.dest.is_dir():\n            parsed_url = urlparse(str(url))\n            file_name = os.path.basename(parsed_url.path)  # default is to use the last bit of the URL","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/download/download_default.py#L466-L502","documentation":"During a resume attempt, the server responded 416 (Range Not Satisfiable) but the local partial file's byte offset did not exactly equal the remote file's total size, so the library cannot trust the partial data. The job is marked resume_required and paused, and DownloadJobCancelledException is raised to force the download to restart from scratch rather than produce a corrupt file. It is a control-flow signal to the download queue, not a fatal failure of the file itself.","triggerScenarios":"A previously interrupted download is resumed (_do_download with resume_from > 0) and the server returns HTTP 416 while the Content-Range total (or job.expected_total_bytes/job.total_bytes) does not match resume_from — e.g. the remote file changed size or was replaced between sessions.","commonSituations":"Mirror/HuggingFace file was updated after the first download attempt; a proxy or CDN returns 416 with inconsistent Content-Range; the local .downloading file was truncated or corrupted externally while the server thinks the range is unsatisfiable.","solutions":["Delete the stale partial file (the `.downloading` file in the destination directory) and resubmit the download so it restarts from byte 0.","Verify the remote URL still points at the same file version (check ETag/Last-Modified or file size); pin a specific revision if the host is a model registry.","Check any proxy/CDN in front of the host that may answer 416 with a mismatched Content-Range; bypass it or disable resume.","Catch DownloadJobCancelledException and inspect job.resume_message to distinguish this from a caller cancellation, then restart the job programmatically."],"exampleFix":"// before: blindly retrying the job, which keeps failing with 416\nqueue.resume(job.id)\n\n// after: discard the partial file, then restart cleanly\nfrom pathlib import Path\npartial = Path(job.local_path).with_suffix('.downloading')\nif partial.exists():\n    partial.unlink()\nqueue.cancel(job.id)\nqueue.submit(job.source, job.dest)","handlingStrategy":"retry","validationCode":"import requests\nhead = requests.head(url, allow_redirects=True)\nremote_size = int(head.headers.get('Content-Length', 0))\npartial = dest_path.with_suffix('.downloading')\nif partial.exists() and remote_size and partial.stat().st_size > remote_size:\n    partial.unlink()  # stale/inconsistent partial, remove before resuming","typeGuard":null,"tryCatchPattern":"try:\n    queue.resume(job.id)\nexcept DownloadJobCancelledException as e:\n    if 'Restart required' in str(e):\n        partial = Path(job.local_path).with_suffix('.downloading')\n        partial.unlink(missing_ok=True)\n        queue.cancel(job.id)\n        queue.submit(job.source, job.dest)","preventionTips":["Pin remote file versions (revision/commit hash on HuggingFace) so files don't change between attempts.","Periodically clean stale `.downloading` files whose size no longer matches remote Content-Length.","Download from mirrors known to answer Range requests consistently."],"tags":["network","http-range","download-resume","http-416"],"backgroundTag":"http-416-range-not-satisfiable","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}