Collection membership rebuild has no projection-failure guard — a swallowed drop silently empties a collection #604

Open
opened 2026-07-25 14:52:11 +02:00 by timothy · 0 comments
Owner

Carved out of the adversarial review of #484 (finding 6). Out of scope there because it is not permanent media deletion, but it is the same seam and the classification work is already done.

Problem

ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs:102-108 rebuilds collection membership destructively: RemoveAllTags(collection) then re-add everything returned by IJellyfinApiClient.GetCollectionItems. That enumeration's mapper is ProjectToCollectionMediaItem, whose terminal catch swallows the exception and drops the item.

After #484, ProjectToCollectionMediaItem correctly classifies its drops (MediaServerProjectionResult: the LocationType != "FileSystem", .strm, and unknown-item.Type arms are Skipped; the catch is Failed) — but GetCollectionItems is passed no MediaServerProjectionFailureCounter (JellyfinApiClient.cs:216-232), so the failure count is computed and thrown away.

Consequence

A projection regression on this path drops items silently. Because the rebuild is remove-all-then-re-add, the dropped items are removed from the collection rather than merely not-added. Every channel whose playout draws on that collection loses the content — and a fully-failing projection empties the collection outright, which is the Collection "X" has no valid items! failure mode seen in #487.

Unlike the library sweeps #484 guards, nothing here is flagged FileNotFound, so EmptyTrashHandler never permanently deletes the media rows — the damage is collection membership, recoverable by a later successful scan. That is why it is lower priority than #484, not why it is harmless.

Emby has the same shape (EmbyCollectionScanner, EmbyApiClient.GetCollectionItems). Plex projections have no Option/catch, so a bad item throws and unwinds rather than dropping silently — likely no change needed there, but confirm.

Fix direction

Thread the existing MediaServerProjectionFailureCounter into GetCollectionItems (the optional-trailing-param shape established in #484) and refuse the destructive rebuild when the count is non-zero, leaving the previous membership in place. MediaServerReconciliationGuard.ShouldFlagMissing may or may not be the right primitive here since this is not an Except-style sweep — decide when picked up rather than forcing the reuse.

Done-when

  • A projection failure during a collection-items enumeration does not result in a destructive membership rebuild
  • Deliberate skips (STRM, non-filesystem, unknown type) still rebuild normally — the #484 invariant holds here too
  • Emby covered as well as Jellyfin; Plex confirmed as not-applicable with a stated reason
  • Adversarial review passed

Ref: #484 review, finding 6. Related: #477, #487.

Carved out of the adversarial review of #484 (finding 6). Out of scope there because it is not permanent *media* deletion, but it is the same seam and the classification work is already done. ## Problem `ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs:102-108` rebuilds collection membership destructively: `RemoveAllTags(collection)` then re-add everything returned by `IJellyfinApiClient.GetCollectionItems`. That enumeration's mapper is `ProjectToCollectionMediaItem`, whose terminal `catch` swallows the exception and drops the item. After #484, `ProjectToCollectionMediaItem` correctly classifies its drops (`MediaServerProjectionResult`: the `LocationType != "FileSystem"`, `.strm`, and unknown-`item.Type` arms are `Skipped`; the `catch` is `Failed`) — but `GetCollectionItems` is passed **no** `MediaServerProjectionFailureCounter` (`JellyfinApiClient.cs:216-232`), so the failure count is computed and thrown away. ## Consequence A projection regression on this path drops items silently. Because the rebuild is remove-all-then-re-add, the dropped items are removed from the collection rather than merely not-added. Every channel whose playout draws on that collection loses the content — and a fully-failing projection empties the collection outright, which is the `Collection "X" has no valid items!` failure mode seen in #487. Unlike the library sweeps #484 guards, nothing here is flagged `FileNotFound`, so `EmptyTrashHandler` never permanently deletes the media rows — the damage is collection membership, recoverable by a later successful scan. That is why it is lower priority than #484, not why it is harmless. Emby has the same shape (`EmbyCollectionScanner`, `EmbyApiClient.GetCollectionItems`). Plex projections have no `Option`/`catch`, so a bad item throws and unwinds rather than dropping silently — likely no change needed there, but confirm. ## Fix direction Thread the existing `MediaServerProjectionFailureCounter` into `GetCollectionItems` (the optional-trailing-param shape established in #484) and refuse the destructive rebuild when the count is non-zero, leaving the previous membership in place. `MediaServerReconciliationGuard.ShouldFlagMissing` may or may not be the right primitive here since this is not an `Except`-style sweep — decide when picked up rather than forcing the reuse. ## Done-when - [ ] A projection failure during a collection-items enumeration does not result in a destructive membership rebuild - [ ] Deliberate skips (STRM, non-filesystem, unknown type) still rebuild normally — the #484 invariant holds here too - [ ] Emby covered as well as Jellyfin; Plex confirmed as not-applicable with a stated reason - [ ] Adversarial review passed Ref: #484 review, finding 6. Related: #477, #487.
timothy added the bugpriority: low labels 2026-07-25 14:52:11 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#604