invoke-ai/InvokeAI · error · OSError

[Errno 17] File {job.download_path} exists

Error message

[Errno 17] File {job.download_path} exists

What it means

On a fresh (non-resume, resume_from == 0) download, if the target file already exists at job.download_path the library refuses to clobber it and raises OSError mimicking errno 17 (EEXIST). This is deliberate: unlike the earlier size-check block, this branch only triggers when resume logic didn't already reconcile the existing file, protecting an existing complete file from being overwritten.

Source

Thrown at invokeai/app/services/download/download_default.py:571

            # Existing file does not match expected size; treat as corrupt and restart.
            self._logger.debug(
                "Resume check: existing file size mismatch; deleting and restarting "
                f"path={job.download_path} existing_size={existing_size} expected={job.total_bytes}"
            )
            job.download_path.unlink()

        free_space = disk_usage(job.download_path.parent).free
        GB = 2**30
        remaining_bytes = max(job.total_bytes - job.bytes, 0)
        if free_space < remaining_bytes:
            raise RuntimeError(
                f"Free disk space {free_space / GB:.2f} GB is not enough for download of {remaining_bytes / GB:.2f} GB."
            )

        # Don't clobber an existing file. See commit 82c2c85202f88c6d24ff84710f297cfc6ae174af
        # for code that instead resumes an interrupted download.
        if job.download_path.exists() and resume_from == 0:
            raise OSError(f"[Errno 17] File {job.download_path} exists")

        # append ".downloading" to the path
        # signal caller that the download is starting. At this point, key fields such as
        # download_path and total_bytes will be populated. We call it here because the might
        # discover that the local file is already complete and generate a COMPLETED status.
        self._signal_job_started(job)

        expected_total = job.total_bytes or job.expected_total_bytes or content_length
        # "range not satisfiable" - local file is at least as large as the remote file
        if resp.status_code == 416 or (expected_total > 0 and job.bytes >= expected_total):
            self._logger.info(f"{job.download_path}: complete file found. Skipping.")
            return

        # "partial content" - local file is smaller than remote file
        elif resp.status_code == 206 or job.bytes > 0:
            self._logger.info(f"{job.download_path}: partial file found. Resuming")

        # some other error

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Resume instead of re-submitting: call the queue's resume/join API so the existing file is reconciled (complete files are skipped).
  2. Delete or rename the existing file at job.download_path if you know it is stale, then re-submit.
  3. Check the existing file's size against the remote content-length — if equal, the download is already done; skip it.
  4. Choose a different dest path if the collision is unintended.

Example fix

// before: re-submitting always
queue.submit(source, dest)

// after: skip if already present and complete
if not dest.exists():
    queue.submit(source, dest)
else:
    logger.info('already downloaded: %s', dest)
Defensive patterns

Strategy: validation

Validate before calling

import requests
from pathlib import Path
def already_downloaded(url: str, dest: Path) -> bool:
    if not dest.exists():
        return False
    remote = int(requests.head(url, allow_redirects=True, timeout=10).headers.get('Content-Length', 0))
    return remote > 0 and dest.stat().st_size == remote

Try / catch

try:
    queue.submit(source, dest)
except OSError as e:
    if 'Errno 17' in str(e) or 'exists' in str(e):
        logger.info('%s already present; skipping', dest)

Prevention

When it happens

Trigger: Submitting a download whose explicit dest file path already exists on disk while resume_from == 0 — e.g. re-submitting the same job with cancel instead of resume, or a leftover file from a prior install.

Common situations: Re-running an install script that re-submits already-downloaded models; a prior download crashed leaving the final file in place; two queue entries pointing at the same dest; user copied the file manually before the queue finished.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/b6e1f8ac7be4f7ff. Report an issue: GitHub.