[object Object]

← back to Exo

Handle config.json not found (image models) (#1408)

2fbdb27bb13a7915d75bc3599f638817d4042ce8 · 2026-02-07 03:34:58 +0000 · ciaranbor

## Motivation

When downloading image models, a missing config.json file triggers a
FileNotFoundError inside download_file_with_retry. This error was being
caught by the generic except Exception handler and retried 3 times
before failing. Then, the whole thing would be retried from the start

## Changes

- src/exo/download/download_utils.py: Added FileNotFoundError to the
list of immediately-raised exceptions in download_file_with_retry,
alongside HuggingFaceAuthenticationError. This prevents useless retries
when a file genuinely doesn't exist on the remote.
- src/exo/master/api.py: Wrapped ModelCard.load(model_id) in a
try/except that converts failures into an HTTPException(400) with a
descriptive error message, giving API consumers a clear error response.

## Why It Works


- FileNotFoundError is a deterministic error — the file won't appear on
retry, so re-raising immediately avoids 3 wasted download attempts with
exponential backoff.
- Catching ModelCard.load() failures and returning a 400 HTTP response
prevents unhandled exceptions from surfacing as opaque 500 errors in the
API.

## Test Plan

### Manual Testing

Verified an image model not in model cards does not cause an infinite
error loop

Files touched

Diff

commit 2fbdb27bb13a7915d75bc3599f638817d4042ce8
Author: ciaranbor <81697641+ciaranbor@users.noreply.github.com>
Date:   Sat Feb 7 03:34:58 2026 +0000

    Handle config.json not found (image models) (#1408)
    
    ## Motivation
    
    When downloading image models, a missing config.json file triggers a
    FileNotFoundError inside download_file_with_retry. This error was being
    caught by the generic except Exception handler and retried 3 times
    before failing. Then, the whole thing would be retried from the start
    
    ## Changes
    
    - src/exo/download/download_utils.py: Added FileNotFoundError to the
    list of immediately-raised exceptions in download_file_with_retry,
    alongside HuggingFaceAuthenticationError. This prevents useless retries
    when a file genuinely doesn't exist on the remote.
    - src/exo/master/api.py: Wrapped ModelCard.load(model_id) in a
    try/except that converts failures into an HTTPException(400) with a
    descriptive error message, giving API consumers a clear error response.
    
    ## Why It Works
    
    
    - FileNotFoundError is a deterministic error — the file won't appear on
    retry, so re-raising immediately avoids 3 wasted download attempts with
    exponential backoff.
    - Catching ModelCard.load() failures and returning a 400 HTTP response
    prevents unhandled exceptions from surfacing as opaque 500 errors in the
    API.
    
    ## Test Plan
    
    ### Manual Testing
    
    Verified an image model not in model cards does not cause an infinite
    error loop
---
 src/exo/download/download_utils.py | 2 ++
 src/exo/master/api.py              | 7 ++++++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/exo/download/download_utils.py b/src/exo/download/download_utils.py
index e0554cd4..7974d504 100644
--- a/src/exo/download/download_utils.py
+++ b/src/exo/download/download_utils.py
@@ -457,6 +457,8 @@ async def download_file_with_retry(
             )
         except HuggingFaceAuthenticationError:
             raise
+        except FileNotFoundError:
+            raise
         except HuggingFaceRateLimitError as e:
             if attempt == n_attempts - 1:
                 raise e
diff --git a/src/exo/master/api.py b/src/exo/master/api.py
index 935c41ea..ce98c354 100644
--- a/src/exo/master/api.py
+++ b/src/exo/master/api.py
@@ -386,7 +386,12 @@ class API:
         if len(list(self.state.topology.list_nodes())) == 0:
             return PlacementPreviewResponse(previews=[])
 
-        model_card = await ModelCard.load(model_id)
+        try:
+            model_card = await ModelCard.load(model_id)
+        except Exception as exc:
+            raise HTTPException(
+                status_code=400, detail=f"Failed to load model card: {exc}"
+            ) from exc
         instance_combinations: list[tuple[Sharding, InstanceMeta, int]] = []
         for sharding in (Sharding.Pipeline, Sharding.Tensor):
             for instance_meta in (InstanceMeta.MlxRing, InstanceMeta.MlxJaccl):

← 3f57416d Add image lightbox (#1414)  ·  back to Exo  ·  Address GPU timeouts (#1429) 4abdaaf7 →