# 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//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//poster?url=metadata://posters/tv.plex.agents.series_ ``` 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.