Add plex collection: health check and poster repair
Plex on odin had 101 of 283 TV shows showing grey placeholder posters. The
cause was not missing images: for every affected show the poster was still
downloaded in the item's metadata bundle, but no candidate was marked
`selected` in the database, so the thumb URL resolved to a 404. Working shows
had exactly one selected candidate; all 101 affected had zero.
Two details make this easy to misdiagnose, so both are captured in the docs:
only 6 items lacked a `thumb` field outright, while 95 advertised a
well-formed thumb URL that 404s on fetch — a metadata-only audit reports the
library as healthy. And a bulk metadata refresh, the obvious suspect, does not
correlate with the damage.
Adds three scripts against the Plex HTTP API (no SSH to odin needed):
plex-health read-only report — server, updates, libraries, unmatched
media, Butler tasks, active streams; exit 0/1/2
poster-audit read-only; fetches every thumb to find the 404s
poster-repair re-selects the locally-cached poster; --dry-run, idempotent,
skips items whose poster already works so manually-chosen
artwork is never replaced
All 283 TV Shows posters now resolve. The health check also surfaced one
unmatched movie, Battlestar Galactica the Mini Series.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
28e0fdb631
commit
3df1cca91c
@@ -0,0 +1,95 @@
|
||||
# TV Shows: 101 missing posters (investigated 2026-08-15)
|
||||
|
||||
What the symptom looked like, what it actually was, and what is still unknown.
|
||||
|
||||
## Symptom
|
||||
|
||||
Grey placeholder posters scattered through the **TV Shows** library in the Plex UI.
|
||||
|
||||
## Scope
|
||||
|
||||
| Library | Type | Affected |
|
||||
| --- | --- | --- |
|
||||
| TV Shows (id 2) | shows | **101 of 283** (36%) |
|
||||
| TV Shows (id 2) | seasons | 0 of 954 |
|
||||
| Movies (id 1) | movies | 0 (sampled 60 of 1059) |
|
||||
| Movies 4k (id 3) | movies | 0 (sampled 40 of 79) |
|
||||
|
||||
Only show-level posters, only in one library. Seasons and episodes were untouched, which
|
||||
is why the library still looked half-normal.
|
||||
|
||||
## The two failure modes
|
||||
|
||||
They are indistinguishable in the UI, and only one is visible to a metadata-only audit:
|
||||
|
||||
| Mode | Count | What it looks like in the API |
|
||||
| --- | --- | --- |
|
||||
| `MISSING` | 6 | the item has no `thumb` field at all |
|
||||
| `BROKEN` | 95 | the item **has** a `thumb` field, but fetching it returns **404** |
|
||||
|
||||
The 95 BROKEN shows are the trap. `/library/sections/2/all` reports a perfectly
|
||||
well-formed `thumb` URL for each one — e.g. `/library/metadata/13343/thumb/1785398727` —
|
||||
and only an actual HTTP request reveals it serves an 85-byte HTML 404 page.
|
||||
|
||||
## Root cause: a lost selection pointer, not a lost file
|
||||
|
||||
For every affected show, `/library/metadata/<key>/posters` still returned a full
|
||||
candidate list — 85 to 158 entries — **including the agent's own poster already
|
||||
downloaded into the item's metadata bundle** as a `metadata://posters/...` entry.
|
||||
|
||||
What was gone was the selection:
|
||||
|
||||
```
|
||||
affected shows with zero `selected: true` candidates: 101 / 101
|
||||
control sample of working shows, selected count: 1 each (15 / 15)
|
||||
affected shows with the poster still in the bundle: 100 / 101
|
||||
```
|
||||
|
||||
So the images were never deleted. The database simply no longer marked *any* candidate as
|
||||
selected for those items, and a show with nothing selected has no poster to serve.
|
||||
|
||||
Because the file was still local, the repair was a pure database operation — instant, no
|
||||
internet round trip, no re-download:
|
||||
|
||||
```bash
|
||||
PUT /library/metadata/<key>/poster?url=metadata://posters/tv.plex.agents.series_<hash>
|
||||
```
|
||||
|
||||
Validated on one show (Severance, `13343`) before touching the rest: `thumb` went from
|
||||
`404` to `200 image/jpeg`, 533 KB.
|
||||
|
||||
## Hypotheses tested and rejected
|
||||
|
||||
- **Unmatched media.** Rejected: all 283 shows had real `plex://show/...` GUIDs; none had
|
||||
a `local://` GUID. The affected shows also had full season and episode counts.
|
||||
- **A bad bulk metadata refresh.** A refresh clearly did run — 77 shows share an
|
||||
`updatedAt` inside a 35-second window (~2026-07-30). But it does not correlate with the
|
||||
damage: of the items in that window 54 are fine and 23 are broken, while 72 broken items
|
||||
sit outside it entirely. Rejected as *the* cause.
|
||||
- **A stale/expired thumb URL format.** Rejected: broken and working items had
|
||||
structurally identical `thumb` URLs with timestamps from the same refresh.
|
||||
|
||||
## Still unknown
|
||||
|
||||
**Which event cleared the selection flags.** Establishing that needs the Plex server logs
|
||||
on odin (`Plex Media Server.log`), which requires filesystem access to the container's
|
||||
appdata — not available from `dev` at the time of writing (no SSH key is authorized on
|
||||
odin). The `CleanOldBundles` Butler task is enabled on a 7-day interval and is the obvious
|
||||
suspect to check first, but note that it does **not** fit the evidence cleanly: the poster
|
||||
files survived in the bundles: only the database pointers were lost.
|
||||
|
||||
Worth watching: if posters rot again on roughly a 7-day cadence, that points at a Butler
|
||||
task; if it correlates with a Plex version bump, it points at a migration.
|
||||
|
||||
## Repair record
|
||||
|
||||
| Step | Result |
|
||||
| --- | --- |
|
||||
| Validation on Severance | poster restored, verified `200 image/jpeg` |
|
||||
| Bulk repair, local candidates | 100 shows restored |
|
||||
| True Detective (no local candidate) | restored via `--allow-remote` from the agent |
|
||||
| Final audit | **283 / 283 posters resolve** |
|
||||
|
||||
Tooling built from this: [../bin/poster-audit](../bin/poster-audit) and
|
||||
[../bin/poster-repair](../bin/poster-repair). Re-run the audit if the symptom returns —
|
||||
it is read-only and will say in a couple of minutes whether it is the same failure.
|
||||
Reference in New Issue
Block a user