Files
scripts/plex/docs/tv-poster-incident.md
T
Lucas BergerandClaude Opus 5 3df1cca91c 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>
2026-08-15 21:15:06 -04:00

96 lines
4.1 KiB
Markdown

# 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.