From 9f502793c1194bfcbbb652c61849d8e17467cd91 Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Fri, 6 Feb 2026 05:51:54 -0600 Subject: [PATCH] fix: retry downloads on transient errors instead of breaking (#1398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Motivation `download_file_with_retry()` has a `break` in the generic exception handler that exits the retry loop after the first transient failure. This means network timeouts, connection resets, and server errors all cause an immediate download failure — the two remaining retry attempts never run. ## Changes **download_utils.py**: Replaced `break` with logging and exponential backoff in the generic exception handler, matching the existing rate-limit handler behavior. Before: ```python except Exception as e: on_connection_lost() if attempt == n_attempts - 1: raise e break # exits loop immediately ``` After: ```python except Exception as e: on_connection_lost() if attempt == n_attempts - 1: raise e logger.error(f"Download error on attempt {attempt + 1}/{n_attempts} ...") logger.error(traceback.format_exc()) await asyncio.sleep(2.0**attempt) ``` ## Why It Works The `break` statement was bypassing the retry mechanism entirely. Replacing it with the same log-and-backoff pattern used by the `HuggingFaceRateLimitError` handler means all 3 attempts are actually used before giving up. The exponential backoff (1s, 2s) gives transient issues time to resolve between attempts. ## Test Plan ### Manual Testing - Downloads that hit transient network errors now retry instead of failing immediately ### Automated Testing - `uv run basedpyright` — 0 errors - `uv run ruff check` — passes - `uv run pytest src/exo/download/tests/ -v` — 11 tests pass --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: rltakashige --- src/exo/download/download_utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/exo/download/download_utils.py b/src/exo/download/download_utils.py index 618e4f38..5a8feeb9 100644 --- a/src/exo/download/download_utils.py +++ b/src/exo/download/download_utils.py @@ -378,10 +378,14 @@ async def download_file_with_retry( logger.error(traceback.format_exc()) await asyncio.sleep(2.0**attempt) except Exception as e: - on_connection_lost() if attempt == n_attempts - 1: + on_connection_lost() raise e - break + logger.error( + f"Download error on attempt {attempt + 1}/{n_attempts} for {model_id=} {revision=} {path=} {target_dir=}" + ) + logger.error(traceback.format_exc()) + await asyncio.sleep(2.0**attempt) raise Exception( f"Failed to download file {model_id=} {revision=} {path=} {target_dir=}" )