affaan-m/ECC · error · FalError

download length does not match declared size

Error message

download length does not match declared size

What it means

download() in falapi.py streams the remote file to a temp file while counting bytes. If the server declared a Content-Length and the streamed total differs from it, the file is incomplete or corrupted, so download() raises FalError instead of promoting a truncated/corrupt file to the destination. The temp file is removed and the existing destination is left untouched.

Solutions

  1. Re-run the download; transient truncation is usually fixed on retry.
  2. Check network stability / disable proxies or VPNs that can truncate or re-encode responses.
  3. Verify the server's Content-Length matches the actual resource size (curl -I) and compare with the expected value passed in.
  4. If the source systematically reports a wrong size, fetch the resource from a mirror or updated URL.

Example fix

# before (fragile, no retry)
api.download(url, dest, expected=size)

# after (bounded retry for transient truncation)
for attempt in range(3):
    try:
        api.download(url, dest, expected=size)
        break
    except api.FalError:
        if attempt == 2:
            raise
        time.sleep(2 ** attempt)
Defensive patterns

Strategy: retry

Validate before calling

import os
head = urllib.request.urlopen(url)
expected = int(head.headers.get('Content-Length', 0))
if expected > MAX_ALLOWED:
    raise ValueError('file too large')
os.remove(dest) if os.path.exists(dest) else None

Try / catch

for attempt in range(3):
    try:
        api.download(url, dest, expected=size)
        break
    except api.FalError as e:
        if attempt == 2 or 'exceeds' in str(e):
            raise
        time.sleep(2 ** attempt)

Prevention

When it happens

Trigger: Calling download(url, dest, expected=<size>) (or downloading from a URL whose Content-Length is known) when the connection drops mid-stream, the server sends fewer/more bytes than declared, or a proxy rewrites the body without updating Content-Length.

Common situations: Flaky networks or Wi-Fi drops during a large fal.ai media download; CDN edge truncation; content-encoding (gzip) double-counting bytes; server-side timeouts on slow links.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/dabc448ff5444619. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/falapi.py:715

        with opener.open(req, timeout=300) as resp:
            declared = getattr(resp, "headers", {}).get("Content-Length")
            expected = int(declared) if declared is not None else None
            if expected is not None and not 0 <= expected <= MAX_DOWNLOAD_BYTES:
                raise FalError("download declares an invalid or excessive size")
            with tempfile.NamedTemporaryFile(dir=dest.parent, prefix=".taste-download-",
                                             delete=False) as fh:
                temporary = Path(fh.name)
                total = 0
                while True:
                    chunk = resp.read(min(1024 * 1024, MAX_DOWNLOAD_BYTES - total + 1))
                    if not chunk:
                        break
                    total += len(chunk)
                    if total > MAX_DOWNLOAD_BYTES:
                        raise FalError("download exceeds maximum allowed size")
                    fh.write(chunk)
                if expected is not None and total != expected:
                    raise FalError("download length does not match declared size")
        os.replace(temporary, dest)
        temporary = None
    except FalError:
        raise
    except Exception:
        raise FalError("download failed; existing destination preserved") from None
    finally:
        if temporary is not None:
            temporary.unlink(missing_ok=True)
    return dest


# ---------------------------------------------------------------------------
# dry-run stubs
# ---------------------------------------------------------------------------


def _digest(*parts: Any) -> str:

View on GitHub (pinned to 8321021c54)