Skip to content

fix(scanners): ignore unknown seasons in availability rollup and skip empty placeholder seasons#2958

Open
fallenbagel wants to merge 1 commit intodevelopfrom
fallenbagel/fix/orphan-empty-seasons-availability
Open

fix(scanners): ignore unknown seasons in availability rollup and skip empty placeholder seasons#2958
fallenbagel wants to merge 1 commit intodevelopfrom
fallenbagel/fix/orphan-empty-seasons-availability

Conversation

@fallenbagel
Copy link
Copy Markdown
Collaborator

@fallenbagel fallenbagel commented Apr 26, 2026

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_AVAILABLE even 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 in processShow then refused to promote the title because those orphan seasons sat there with no signal.

This PR addresses the issue from two angles.

  • The Plex and Jellyfin scanners now skip TMDB seasons with no episode count, so empty placeholders never enter the DB in the first place.
  • The rollup in processShow now treats UNKNOWN seasons as neutral rather than blockers, with a guard ensuring at least one season is genuinely available so an all-UNKNOWN show 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?

  • I ran this change directly on my prod on a tv series with an empty placeholder season:
image - Ran jellyfin full scan and now its marked as available instead of partially available.

Screenshots / Logs (if applicable)

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy)
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

Summary by CodeRabbit

  • Bug Fixes
    • Improved TV show availability detection by better handling unknown status states and requiring at least one available season to be present.
    • Fixed show processing for Jellyfin and Plex sources by excluding seasons with no episodes from being processed.

… 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.
@fallenbagel fallenbagel requested a review from a team as a code owner April 26, 2026 19:53
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
TV Availability Logic
server/lib/scanners/baseScanner.ts
Reworked overall availability determination to exclude MediaStatus.UNKNOWN seasons and require at least one non-special season marked as AVAILABLE (standard or 4K status), replacing prior "all non-special seasons must be AVAILABLE" logic.
Season Filtering in Scanners
server/lib/scanners/jellyfin/index.ts, server/lib/scanners/plex/index.ts
Extended season selection to filter out seasons with episode_count <= 0 in addition to existing special-season filtering, preventing zero-episode seasons from being processed during show metadata matching.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

bug, severity: medium

Suggested reviewers

  • gauthier-th
  • 0xSysR3ll

Poem

🐰 Seasons now sorted with wisdom so bright,
Unknown status won't block the light,
Zero episodes vanish away,
Availability blooms, a cleaner display! 🌱

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: ignoring unknown seasons in availability rollup and skipping empty placeholder seasons, which directly addresses the core issue preventing show promotion to AVAILABLE status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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 both status and status4k. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 60a2a87 and b2bcbb9.

📒 Files selected for processing (3)
  • server/lib/scanners/baseScanner.ts
  • server/lib/scanners/jellyfin/index.ts
  • server/lib/scanners/plex/index.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant