Summary
On a server with large TV and Music libraries, the Libraries page shows only Movies. TV (55,738 episodes) and Music (193,414 tracks) are missing entirely — no error, no warning, no log line. The summary tiles are wrong to match ("Libraries: 1", "Total Items: 3,349", "Storage Used: 2.57 TB").
The cause is two things combining:
LibraryOverviewService::mediaItems() enumerates every item in a library — full Fields=MediaSources,RunTimeTicks,DateCreated objects, paged 500 at a time — in order to build one library card.
JellyfinClient::getJson() has a hardcoded 8-second timeout with no config override, and LibraryOverviewService::data() wraps each library in catch (\Throwable) { continue; }.
So any library whose 500-item page cannot be served by Jellyfin in under 8 seconds is dropped without a trace.
Environment
|
|
| Jellydash |
1.6.0 (ghcr.io/themartz90/jellydash@sha256:8bd83201bf9e0532bc7be9c0881a2f20468853c5a899cad7c8c58888b7618ded, built 2026-08-23) |
| Deployment |
Docker, SQLite (DB_DRIVER=sqlite3) |
| Jellyfin |
10.11.11, x64, Linux |
| PHP |
8.3.6, memory_limit=256M, max_execution_time=0 (CLI warmer) |
LIBRARIES_CACHE_TTL |
300 (default) |
| Auth |
Admin API token |
Jellyfin and Jellydash run as containers on the same host, on the same Docker bridge network.
Reproduction
Run the app's own code path inside the container:
$c = new \Mk\Framework\Jellyfin\JellyfinClient();
$c->items([
'ParentId' => '<tv library id>',
'Recursive' => 'true',
'IncludeItemTypes' => 'Episode',
'Fields' => 'MediaSources,RunTimeTicks,DateCreated',
]);
Result (consistent across repeated runs):
memory_limit=256M max_execution_time=0
TV THREW after 8.0s: RuntimeException: Jellyfin request failed:
Operation timed out after 8000 milliseconds with 0 bytes received peak=2MB
Music THREW after 8.0s: RuntimeException: Jellyfin request failed:
Operation timed out after 8001 milliseconds with 0 bytes received peak=2MB
Note peak=2MB — this is not a memory problem, purely the timeout.
Timings
Measured from inside the Jellydash container with CURLOPT_TIMEOUT=300, issuing the exact query items() builds (one page, Limit=500):
| Library |
TotalRecordCount |
One 500-item page |
Pages |
Full scan |
| Movies |
3,349 |
10.0s |
7 |
~70s |
| TV |
55,738 |
13.9s |
112 |
~26 min |
| Music |
193,414 |
49.8s |
387 |
~5.4 hrs |
All three exceed the 8s limit. Movies currently renders only because its cache was warmed at a moment when Jellyfin answered faster; at 10.0s it is now over the line too and disappears on some refresh cycles.
This is why raising the timeout is not on its own a fix: at ~50s/page, Music needs roughly 5.4 hours for one full scan, against a default LIBRARIES_CACHE_TTL of 300 seconds. It would never converge.
Two suggested fixes
1. Don't enumerate items to produce aggregates.
Everything the library card needs is available from TotalRecordCount without transferring any items — and the codebase already does exactly this elsewhere. breakdown() uses countItems() → JellyfinClient::itemCount() (Limit=0), which is a single cheap call per type. The main card path in mediaItems() could use the same mechanism.
The one genuine exception is sizeBytes, which today is summed from each item's MediaSources. Jellyfin can't total that directly, so options are to keep the enumeration behind an explicit opt-in, fetch it with a much narrower Fields set, or drop the storage tile for libraries above some size. RunTimeTicks has the same shape — though for totalTime on a 193k-track library, an approximation or omission seems more useful than a 5-hour scan.
2. Don't fail silently.
catch (\Throwable) { continue; } at LibraryOverviewService.php:78 is what makes this so hard to diagnose from the outside — a library vanishes from the UI with no indication anything went wrong. Even keeping current behaviour, logging the library name and exception, and rendering a card in an error state ("couldn't load — timed out") would turn this from a mystery into something self-evident.
A configurable timeout (e.g. JELLYFIN_TIMEOUT) would also help operators on slower or larger servers, though per the timings above it isn't sufficient alone.
Relevant code
Scope
Worth saying: this affects the Libraries page only. items() has exactly one caller in the codebase (mediaItems()), so Now Playing, History, Statistics, Trending and the Jellyseerr page are all unaffected and work correctly here — the history poller records streams normally and 40 Jellyseerr requests synced without trouble. Thanks for the project.
Summary
On a server with large TV and Music libraries, the Libraries page shows only Movies. TV (55,738 episodes) and Music (193,414 tracks) are missing entirely — no error, no warning, no log line. The summary tiles are wrong to match ("Libraries: 1", "Total Items: 3,349", "Storage Used: 2.57 TB").
The cause is two things combining:
LibraryOverviewService::mediaItems()enumerates every item in a library — fullFields=MediaSources,RunTimeTicks,DateCreatedobjects, paged 500 at a time — in order to build one library card.JellyfinClient::getJson()has a hardcoded 8-second timeout with no config override, andLibraryOverviewService::data()wraps each library incatch (\Throwable) { continue; }.So any library whose 500-item page cannot be served by Jellyfin in under 8 seconds is dropped without a trace.
Environment
ghcr.io/themartz90/jellydash@sha256:8bd83201bf9e0532bc7be9c0881a2f20468853c5a899cad7c8c58888b7618ded, built 2026-08-23)DB_DRIVER=sqlite3)memory_limit=256M,max_execution_time=0(CLI warmer)LIBRARIES_CACHE_TTLJellyfin and Jellydash run as containers on the same host, on the same Docker bridge network.
Reproduction
Run the app's own code path inside the container:
Result (consistent across repeated runs):
Note
peak=2MB— this is not a memory problem, purely the timeout.Timings
Measured from inside the Jellydash container with
CURLOPT_TIMEOUT=300, issuing the exact queryitems()builds (one page,Limit=500):TotalRecordCountAll three exceed the 8s limit. Movies currently renders only because its cache was warmed at a moment when Jellyfin answered faster; at 10.0s it is now over the line too and disappears on some refresh cycles.
This is why raising the timeout is not on its own a fix: at ~50s/page, Music needs roughly 5.4 hours for one full scan, against a default
LIBRARIES_CACHE_TTLof 300 seconds. It would never converge.Two suggested fixes
1. Don't enumerate items to produce aggregates.
Everything the library card needs is available from
TotalRecordCountwithout transferring any items — and the codebase already does exactly this elsewhere.breakdown()usescountItems()→JellyfinClient::itemCount()(Limit=0), which is a single cheap call per type. The main card path inmediaItems()could use the same mechanism.The one genuine exception is
sizeBytes, which today is summed from each item'sMediaSources. Jellyfin can't total that directly, so options are to keep the enumeration behind an explicit opt-in, fetch it with a much narrowerFieldsset, or drop the storage tile for libraries above some size.RunTimeTickshas the same shape — though fortotalTimeon a 193k-track library, an approximation or omission seems more useful than a 5-hour scan.2. Don't fail silently.
catch (\Throwable) { continue; }atLibraryOverviewService.php:78is what makes this so hard to diagnose from the outside — a library vanishes from the UI with no indication anything went wrong. Even keeping current behaviour, logging the library name and exception, and rendering a card in an error state ("couldn't load — timed out") would turn this from a mystery into something self-evident.A configurable timeout (e.g.
JELLYFIN_TIMEOUT) would also help operators on slower or larger servers, though per the timings above it isn't sufficient alone.Relevant code
JellyfinClient.php:345—getJson(string $path, int $timeout = 8), the hardcoded defaultJellyfinClient.php:60—items(), the paged enumerator, callsgetJson()with no timeout overrideJellyfinClient.php:86—itemCount(), the cheapLimit=0path that already existsLibraryOverviewService.php:78—catch (\Throwable) { continue; }LibraryOverviewService.php:342—mediaItems()Scope
Worth saying: this affects the Libraries page only.
items()has exactly one caller in the codebase (mediaItems()), so Now Playing, History, Statistics, Trending and the Jellyseerr page are all unaffected and work correctly here — the history poller records streams normally and 40 Jellyseerr requests synced without trouble. Thanks for the project.