Unified search fails entirely when TMDB is unreachable #168

Closed
opened 2026-08-24 15:25:30 +01:00 by naps62-yolo · 1 comment
Owner

Found while smoke-testing the Feedback pass milestone against a local build
with TMDB stubbed out. Pre-existing on main; nothing in that milestone
caused it.

/api/search returns 503 when TMDB is unreachable

search in crates/arr-api/src/search.rs runs the library queries first, then
calls TMDB:

let tmdb = tmdb_client(&state)?;
let results = search_tmdb(&tmdb, kind, input).await?;

The ? propagates, so an unreachable TMDB discards the library results that
were already in hand and the whole request fails. Observed: every
GET /api/search?q=… returning 503 while the daemon was otherwise healthy and
serving /api/series normally.

§9.2 puts the library set first and TMDB second, and the search box is the
primary way into the app. Losing the in-library half of it because an upstream
is down is the wrong trade — the request should return what it has, with the
TMDB group empty, and say why.

The error string names the wrong subsystem

ApiError::Unavailable renders as {"error":"database unavailable"}
(crates/arr-api/src/movies.rs:185), but the same variant is returned for a
missing TMDB key, a missing Prowlarr key, a Prowlarr indexer listing failure
and an unreachable upstream. The message sent me looking at SQLite when the
database was fine and TMDB was the thing that was down. The title detail page
shows it to the user as metadata unavailable — database unavailable.

What to change

  • Return the library results with an empty tmdb group when the TMDB call
    fails, rather than failing the request. The response already carries the two
    groups separately, so a client can render the library half unchanged; decide
    whether it needs a field saying the TMDB half is degraded, or whether the
    health endpoint covers it.
  • Split ApiError::Unavailable so the message names what is actually
    unavailable. At minimum the database case and the upstream case should read
    differently.

Non-goals

  • No caching or retry policy changes. arr-meta's cache is #158's problem.
  • Manual release search (/api/releases) legitimately cannot proceed without
    Prowlarr — it stays a hard failure, it just needs an accurate message.
Found while smoke-testing the Feedback pass milestone against a local build with TMDB stubbed out. Pre-existing on `main`; nothing in that milestone caused it. ## `/api/search` returns 503 when TMDB is unreachable `search` in `crates/arr-api/src/search.rs` runs the library queries first, then calls TMDB: ```rust let tmdb = tmdb_client(&state)?; let results = search_tmdb(&tmdb, kind, input).await?; ``` The `?` propagates, so an unreachable TMDB discards the library results that were already in hand and the whole request fails. Observed: every `GET /api/search?q=…` returning 503 while the daemon was otherwise healthy and serving `/api/series` normally. §9.2 puts the library set first and TMDB second, and the search box is the primary way into the app. Losing the in-library half of it because an upstream is down is the wrong trade — the request should return what it has, with the TMDB group empty, and say why. ## The error string names the wrong subsystem `ApiError::Unavailable` renders as `{"error":"database unavailable"}` (`crates/arr-api/src/movies.rs:185`), but the same variant is returned for a missing TMDB key, a missing Prowlarr key, a Prowlarr indexer listing failure and an unreachable upstream. The message sent me looking at SQLite when the database was fine and TMDB was the thing that was down. The title detail page shows it to the user as `metadata unavailable — database unavailable`. ## What to change - Return the library results with an empty `tmdb` group when the TMDB call fails, rather than failing the request. The response already carries the two groups separately, so a client can render the library half unchanged; decide whether it needs a field saying the TMDB half is degraded, or whether the health endpoint covers it. - Split `ApiError::Unavailable` so the message names what is actually unavailable. At minimum the database case and the upstream case should read differently. ## Non-goals - No caching or retry policy changes. `arr-meta`'s cache is #158's problem. - Manual release search (`/api/releases`) legitimately cannot proceed without Prowlarr — it stays a hard failure, it just needs an accurate message.
naps62-yolo added the area/apiphase/5-uidifficulty/moderatetype/bug labels 2026-08-24 15:25:30 +01:00
naps62-yolo added this to the Reliability and first run milestone 2026-08-24 17:58:27 +01:00
Author
Owner

Fixed on reliability/168-search-degraded for /api/search and /api/releases, plus the SPA search view.

One site left, outside this branch's reach: crates/arr-api/src/series.rs still constructs ApiError::Unavailable for TMDB/Prowlarr faults (lines ~1306-1479), so series surfaces keep rendering the old wrong "database unavailable" string until #176 lands. The new variant is additive — ApiError::Upstream(name) renders {name} unavailable and Unavailable now means only the SQLite handle — so #176 can adopt it by swapping those six call sites.

Fixed on `reliability/168-search-degraded` for `/api/search` and `/api/releases`, plus the SPA search view. One site left, outside this branch's reach: `crates/arr-api/src/series.rs` still constructs `ApiError::Unavailable` for TMDB/Prowlarr faults (lines ~1306-1479), so series surfaces keep rendering the old wrong "database unavailable" string until #176 lands. The new variant is additive — `ApiError::Upstream(name)` renders `{name} unavailable` and `Unavailable` now means only the SQLite handle — so #176 can adopt it by swapping those six call sites.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#168