Jellyfin episodes still present in Jellyfin are flagged FileNotFound and never restored (7675 items, 1557 upcoming playout items on 28 channels) #932

Open
opened 2026-10-27 22:18:35 +01:00 by timothy · 1 comment
Owner

Problem

Jellyfin episodes that still exist in Jellyfin, under the same ItemId, and on disk are flagged MediaItemState.FileNotFound (State=1) in prod, and the flag is not cleared.

Measured 2026-10-27 ~22:30 against prod (jazz, read-only):

  • MediaItem rows with State=1: 7675.
  • Upcoming PlayoutItems (Finish > now) that reference State=1 media: 1557 across 28 channels (Friends 126, Seinfeld 119, Frasier 128, Simpsons 130, Family Guy 130, South Park 120, …). State=0: 2441.
  • Example false flag: MediaItem 37687, Friends S03E11, Jellyfin ItemId b7afd9793d560a4b3e8867b40945b737, State=1. GET /Items?Ids=b7afd979… on Jellyfin returns 1 record with the same path, and the file is present at /data/tvshows/Friends/Season 03/ inside the container.
  • True positive for contrast: MediaItem 14919, QI s18e17 (old /QI/Series 18/ path, id be7bc4bc…). Jellyfin returns 0 records, because the episode now lives at S00E05 under Specials. This one caused ch130's error card at 22:14 (PlayoutItemNotAvailableFromMediaServer); the playout rebuild is handed to media-management.

For a media-server library, FileNotFound means the ItemId is absent from the scan's incoming set (MediaServerTelevisionLibraryScanner: existingEpisodes.Except(incomingItemIds) → FlagFileNotFoundEpisodes). It does not describe the disk. Why a still-present id lands there, and why later scans don't restore Normal, is unmeasured. Do not assume a cause before reproducing.

Impact

  • PlayoutBuilder with skipMissingItems drops these items, so a rebuild can silently empty a healthy channel. Do not mass-rebuild playouts on the strength of the 1557 count.
  • ChannelRepository counts them as Broken in the channel listing.
  • FileNotFoundHealthCheck reports 7675, which hides the real ones.

Done-when

  • Root cause measured: which scan path flags an id Jellyfin still returns, and why the flag persists
  • Fix, with a test that reddens when the fix alone is reverted
  • A recovery path for the existing false flags (a supported rescan or repair, not hand-edited SQL), handed to media-management to run on prod
  • Adversarial review passed

Refs #924, #927

## Problem Jellyfin episodes that still exist in Jellyfin, under the same ItemId, and on disk are flagged `MediaItemState.FileNotFound` (State=1) in prod, and the flag is not cleared. Measured 2026-10-27 ~22:30 against prod (jazz, read-only): - `MediaItem` rows with State=1: **7675**. - Upcoming `PlayoutItem`s (Finish > now) that reference State=1 media: **1557 across 28 channels** (Friends 126, Seinfeld 119, Frasier 128, Simpsons 130, Family Guy 130, South Park 120, …). State=0: 2441. - Example false flag: MediaItem 37687, Friends S03E11, Jellyfin ItemId `b7afd9793d560a4b3e8867b40945b737`, State=1. `GET /Items?Ids=b7afd979…` on Jellyfin returns 1 record with the same path, and the file is present at `/data/tvshows/Friends/Season 03/` inside the container. - True positive for contrast: MediaItem 14919, QI s18e17 (old `/QI/Series 18/` path, id `be7bc4bc…`). Jellyfin returns 0 records, because the episode now lives at S00E05 under Specials. This one caused ch130's error card at 22:14 (`PlayoutItemNotAvailableFromMediaServer`); the playout rebuild is handed to media-management. For a media-server library, FileNotFound means the ItemId is absent from the scan's incoming set (`MediaServerTelevisionLibraryScanner`: `existingEpisodes.Except(incomingItemIds)` → `FlagFileNotFoundEpisodes`). It does not describe the disk. Why a still-present id lands there, and why later scans don't restore Normal, is **unmeasured**. Do not assume a cause before reproducing. ## Impact - `PlayoutBuilder` with `skipMissingItems` drops these items, so a rebuild can silently empty a healthy channel. Do not mass-rebuild playouts on the strength of the 1557 count. - `ChannelRepository` counts them as `Broken` in the channel listing. - `FileNotFoundHealthCheck` reports 7675, which hides the real ones. ## Done-when - [ ] Root cause measured: which scan path flags an id Jellyfin still returns, and why the flag persists - [ ] Fix, with a test that reddens when the fix alone is reverted - [ ] A recovery path for the existing false flags (a supported rescan or repair, not hand-edited SQL), handed to media-management to run on prod - [ ] Adversarial review passed Refs #924, #927
timothy added the priority: medium label 2026-10-27 22:18:35 +01:00
Author
Owner

Evidence from the ch130 playout reset (media-management, 2026-10-27 21:28Z, channel id 12 / playout 11):

Second mechanism: replaced files leave a stale State=1 row. When a file is replaced by one with the same name and a different extension (.avi→.mkv for Penn & Teller, .mkv→.mp4 for House of Games), Jellyfin issues a new ItemId. ErsatzTV adds the new row and flags the old one FileNotFound, then keeps it. That flag is correct, but the row is never retired. All six ch130 flags media-management checked are of this kind.

Why rebuilds still schedule them. PlayoutBuilder skips FileNotFound/Unavailable only when ConfigElement playout.skip_missing_items is true (PlayoutBuilder.cs ~410, default false), and prod has no row for that key. After the reset, ch130 picked 3 different stale rows within 24h: MediaItem 30876 (House of Games S08E13), 44848 (Penn & Teller S02E13) and 30915 (House of Games S08E52). Resetting again only re-rolls which stale rows it picks.

The false flags (this issue's original case) make every obvious remedy unsafe: enabling skip-missing drops falsely flagged healthy items from rebuilds, and Empty Trash permanently deletes them. Prod is on hold, with no reset, deep scan, Empty Trash or skip-missing setting, until this issue is resolved.

Done-when addition: the fix must distinguish the two populations, a real replaced-file row (retire it) and a false flag (restore Normal), and the recovery path must be safe to run on prod with both present.

Evidence from the ch130 playout reset (media-management, 2026-10-27 21:28Z, channel id 12 / playout 11): **Second mechanism: replaced files leave a stale State=1 row.** When a file is replaced by one with the same name and a different extension (`.avi`→`.mkv` for Penn & Teller, `.mkv`→`.mp4` for House of Games), Jellyfin issues a new ItemId. ErsatzTV adds the new row and flags the old one FileNotFound, then keeps it. That flag is correct, but the row is never retired. All six ch130 flags media-management checked are of this kind. **Why rebuilds still schedule them.** `PlayoutBuilder` skips FileNotFound/Unavailable only when ConfigElement `playout.skip_missing_items` is true (`PlayoutBuilder.cs` ~410, default false), and prod has no row for that key. After the reset, ch130 picked 3 different stale rows within 24h: MediaItem 30876 (House of Games S08E13), 44848 (Penn & Teller S02E13) and 30915 (House of Games S08E52). Resetting again only re-rolls which stale rows it picks. **The false flags (this issue's original case) make every obvious remedy unsafe:** enabling skip-missing drops falsely flagged healthy items from rebuilds, and Empty Trash permanently deletes them. Prod is on hold, with no reset, deep scan, Empty Trash or skip-missing setting, until this issue is resolved. Done-when addition: the fix must distinguish the two populations, a real replaced-file row (retire it) and a false flag (restore Normal), and the recovery path must be safe to run on prod with both present.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#932