fix(scanners): ignore unknown seasons in availability rollup and skip empty placeholder seasons#2958
Conversation
… empty placeholder seasons Filter empty TMDB placeholder seasons from Plex/Jellyfin scanners and treat UNKNOWN seasons as neutral in the availability rollup so a stale orphan season can't hold a fully-scanned show at PARTIALLY_AVAILABLE.
📝 WalkthroughWalkthroughThis PR refines season and availability handling across scanner implementations. It modifies TV availability determination in the base scanner to exclude unknown-status seasons while requiring at least one available non-special season, and filters out zero-episode seasons during Jellyfin and Plex show processing. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
server/lib/scanners/baseScanner.ts (1)
456-468: Optional: extract the duplicated rollup predicate.The same three-part predicate (
length > 0 && filter(non-UNKNOWN).every(AVAILABLE) && some(AVAILABLE)) is repeated four times across the existing-media and new-media paths for bothstatusandstatus4k. A small helper would make intent clearer and prevent the two paths from drifting in future edits.♻️ Suggested helper
const isRollupAvailable = <T extends { status: MediaStatus }>( seasons: T[], pick: (s: T) => MediaStatus ): boolean => { if (seasons.length === 0) return false; const known = seasons.filter((s) => pick(s) !== MediaStatus.UNKNOWN); return ( known.every((s) => pick(s) === MediaStatus.AVAILABLE) && seasons.some((s) => pick(s) === MediaStatus.AVAILABLE) ); }; // usage const isAllStandardSeasonsAvailable = isRollupAvailable( nonSpecialSeasons, (s) => s.status ); const isAll4kSeasonsAvailable = isRollupAvailable( nonSpecialSeasons, (s) => s.status4k );Also applies to: 513-527
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/lib/scanners/baseScanner.ts` around lines 456 - 468, Extract the duplicated three-part predicate used to compute isAllStandardSeasonsAvailable and isAll4kSeasonsAvailable (and the similar logic around the other path) into a small reusable helper (e.g., isRollupAvailable) that takes a seasons array and a selector function (pick) returning a MediaStatus; replace the direct expressions in the existing code with calls like isRollupAvailable(nonSpecialSeasons, s => s.status) and isRollupAvailable(nonSpecialSeasons, s => s.status4k) so both the standard and 4k rollups (and the duplicate logic later in the file) use the same centralized predicate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@server/lib/scanners/baseScanner.ts`:
- Around line 456-468: Extract the duplicated three-part predicate used to
compute isAllStandardSeasonsAvailable and isAll4kSeasonsAvailable (and the
similar logic around the other path) into a small reusable helper (e.g.,
isRollupAvailable) that takes a seasons array and a selector function (pick)
returning a MediaStatus; replace the direct expressions in the existing code
with calls like isRollupAvailable(nonSpecialSeasons, s => s.status) and
isRollupAvailable(nonSpecialSeasons, s => s.status4k) so both the standard and
4k rollups (and the duplicate logic later in the file) use the same centralized
predicate.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e9332d2d-02bd-4790-a425-10947bcf9e2e
📒 Files selected for processing (3)
server/lib/scanners/baseScanner.tsserver/lib/scanners/jellyfin/index.tsserver/lib/scanners/plex/index.ts
Description
Shows with empty placeholder seasons on TMDB (e.g. an unaired Season 2 stub created when a returning series is announced) were getting stuck at
PARTIALLY_AVAILABLEeven when every actual season was fully scanned and available. The Plex and Jellyfin scanners were iterating every TMDB season, persisting empty ones into the DB, and the show-level rollup inprocessShowthen refused to promote the title because those orphan seasons sat there with no signal.This PR addresses the issue from two angles.
processShownow treatsUNKNOWNseasons as neutral rather than blockers, with a guard ensuring at least one season is genuinely available so an all-UNKNOWNshow is never falsely promoted.Sonarr is unaffected since it already gates on what Sonarr actively tracks.
The scanner filter prevents future orphans while the rollup change self-heals existing affected rows on the next scan. When an orphan placeholder later gets real episodes upstream, the existing transition logic picks it up normally.
This PR lands in tandem with #2757 and #2850, which target related but distinct symptoms of DB state drifting from media-server reality. #2757 resets orphaned processing shows and movies whose Radarr/Sonarr entries have been deleted, producing more legitimate UNKNOWN states that benefit directly from the neutral rollup treatment here. #2850 tightens the season existence check in availability sync so ghost seasons get correctly transitioned to deleted.
Each PR is independently mergeable, but would highly recommend merging all three together for the cleanest end-state.
How Has This Been Tested?
Screenshots / Logs (if applicable)
Checklist:
pnpm buildpnpm i18n:extractSummary by CodeRabbit