affaan-m/ECC · error · FalError

download failed; existing destination preserved

Error message

download failed; existing destination preserved

What it means

download() writes to a temporary file in the destination directory and only atomically renames it onto dest after a fully successful transfer. Any non-FalError exception during the request or write (network error, DNS failure, permission error, decode error) is converted into this generic FalError; because the rename never happened, the pre-existing destination file is preserved untouched.

Solutions

  1. Check network connectivity and retry the download
  2. Verify dest.parent exists and is writable by the current user (mkdir -p, chmod)
  3. Increase the hardcoded timeout=300 in download() for large assets
  4. Run the underlying urllib opener manually to see the original exception (this handler discards it via 'from None')

Example fix

# before
path = download(url, dest)  # opaque 'download failed' on any error
# after
try:
    path = download(url, dest)
except FalError:
    dest.parent.mkdir(parents=True, exist_ok=True)
    import urllib.request
    urllib.request.urlretrieve(url, '/tmp/dbg-asset')  # surface real error
    raise
Defensive patterns

Strategy: try-catch

Validate before calling

assert dest.parent.exists() and os.access(dest.parent, os.W_OK), 'destination dir must be writable'

Try / catch

try:
    download(url, dest)
except FalError as e:
    if 'download failed' in str(e):
        # real cause hidden; retry with a plain client to surface it
        urllib.request.urlretrieve(url, dest.with_suffix('.tmp'))
    raise

Prevention

When it happens

Trigger: DNS resolution failure, connection reset/timeouts during opener.open or resp.read, SSL errors, permission denied writing into dest.parent, disk-full while writing the temp file, or urllib raising any unexpected exception.

Common situations: Offline/flaky network during a pipeline run; destination directory is read-only or owned by another user; long download exceeding the hardcoded timeout=300; antivirus or OS locking temp files in dest.parent.

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/d86265261f33f937. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/taste/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)