affaan-m/ECC · error · FalError

download failed; existing destination preserved

Error message

download failed; existing destination preserved

What it means

download() wraps its whole download loop in a broad except: any non-FalError exception (network I/O error, DNS failure, timeout, disk full, permission error) is converted to FalError with the message 'download failed; existing destination preserved'. The handler re-raises FalError unchanged, and a finally block unlinks the temp file, so a failed download never clobbers a pre-existing destination file.

Solutions

  1. Check that the destination directory exists and is writable; create the parent with Path(dest).parent.mkdir(parents=True, exist_ok=True).
  2. Test connectivity to the fal.ai/CDN host (curl -I the URL).
  3. Free disk space if the drive is full — large media downloads commonly hit ENOSPC.
  4. Retry with backoff; transient network faults are the dominant cause.
  5. Inspect the suppressed underlying exception by reproducing the fetch outside download() (e.g. urllib) to see the real cause, since the handler hides it with `from None`.

Example fix

// before
dest.parent.mkdir(parents=True, exist_ok=True)  # missing
api.download(url, dest)

// after
dest = Path(dest)
dest.parent.mkdir(parents=True, exist_ok=True)
try:
    api.download(url, dest)
except api.FalError as e:
    print('download failed, previous file kept:', e)
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path
import shutil

def can_download_to(dest):
    dest = Path(dest)
    dest.parent.mkdir(parents=True, exist_ok=True)
    if not os.access(dest.parent, os.W_OK):
        return False
    return shutil.disk_usage(dest.parent).free > MIN_FREE_BYTES

Try / catch

try:
    api.download(url, dest)
except api.FalError as e:
    log.warning('download failed, existing file kept: %s', e)
    # fall back to cached dest or re-raise

Prevention

When it happens

Trigger: Any exception during download(url, dest) other than the explicit size/limit checks: socket timeouts, connection reset, HTTP errors surfaced as exceptions, unwritable temp directory, disk-full during fh.write, or os.replace failing.

Common situations: Server outage or 5xx from the CDN; no internet/DNS in a container; full disk when saving large video files; read-only working directory; wrong destination path (parent missing).

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

                                             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:
    h = hashlib.sha256("|".join(str(p) for p in parts).encode("utf-8"))
    return h.hexdigest()[:12]


def _stub_from_schema(schema: dict) -> dict:
    """Build a stub object with the same keys and types as ``schema``."""

View on GitHub (pinned to 8321021c54)