Skip to content

Finalize Jellyfin hardening for collections, search, and setup#2534

Closed
enoch85 wants to merge 34 commits into
developmentfrom
fix/jellyfin-stability-hardening
Closed

Finalize Jellyfin hardening for collections, search, and setup#2534
enoch85 wants to merge 34 commits into
developmentfrom
fix/jellyfin-stability-hardening

Conversation

@enoch85

@enoch85 enoch85 commented Mar 26, 2026

Copy link
Copy Markdown
Collaborator

Why

This branch started as a fix for a few Jellyfin stability issues, then was extended to close the still-valid gaps that remained after the Jellyfin merge into 3.0.

What this PR fixes

  • scopes Jellyfin collection lookup to the requested library instead of returning cross-library collections
  • validates collection endpoint payloads with Zod before they reach the service layer
  • avoids implicitly selecting a media server when both Plex and Jellyfin are configured without an explicit media_server_type
  • makes Jellyfin search results hydrate through real metadata instead of returning fabricated partial media items
  • fails fast when a media server adapter still is not set up after initialize() instead of returning a broken adapter
  • adds focused regression coverage for the affected Jellyfin and factory behaviors

Why this is better

This makes Jellyfin behavior more predictable in the places that matter most:

  • collection operations stay library-correct
  • invalid collection requests fail at the API boundary
  • mixed Plex/Jellyfin configs no longer produce unsafe implicit selection
  • search results now carry real provider IDs and metadata instead of placeholders
  • adapter startup failures surface immediately instead of leaking into later request handling

Validation

  • targeted Jellyfin adapter and media server factory specs passed
  • full server test suite passed: 39 suites, 651 tests

This PR targets jellyfin-dev and is intended to be the final Jellyfin hardening pass for the remaining verified issues in that branch line.

PR #2396 - feat(settings): unify connection-test error handling across services
- Added shared backend/frontend error helpers for connection tests
- Reduced connection test timeout to 3s and parallelized tests
- Normalized user-facing error messages across all service test endpoints

PR #2429 - fix: emit collection notification events only after successful operations
- Moved event emissions to after operations complete
- Only emits events for items that were truly added/removed
- Pass touchedMediaServerIds to prevent incorrect removals during sync

PR #2436 - fix(server): resolve Seerr addUser returning NULL
- Use username fields directly from Seerr API response instead of media server ID lookup
- Use workspace-local typeorm migration cli
- Use initial setup terminology for media server success log
- Added 11 unit tests for addUser covering all user types and edge cases
PR #2438 - feat(rules): add rule Jellyfin favorited by including parents
- Added Jellyfin favorited rule that checks parent items (series/seasons)
- Includes unit tests for the new favorited-by-parents getter

PR #2439 - fix: stop rule execution when rule group is deleted mid-run
- Changed onRuleGroupDeleted to call stopProcessingRuleGroup instead of removeFromQueue
- Fires abort signal for in-flight execution, preventing null reference crashes
- Added unit test for delete-during-execution scenario

PR #2442 - fix(server): reject null/undefined in numeric rule comparisons
- Fixed null guard using proper nullish check (`firstVal != null`)
- Preserves v2.27.0 null→0 coercion for BIGGER/SMALLER to avoid upgrade breakage
- Logs warning when non-number values are encountered in numeric comparisons
- Updated regression tests for numeric comparisons including null/undefined cases
Adds a secondary ORDER BY on `id` to prevent duplicate and missing items
when paginating collection media. Items sharing the same `addDate` could
appear on multiple pages or be skipped entirely due to SQLite's
non-deterministic row ordering for equal sort values.

Closes #2443
PR #2445 - fix: prevent false watchlist evaluation when plex.tv is unreachable
- Guard PlexTvApi.getUsers() and getUser() against undefined responses from timed-out plex.tv requests
- Detect missing UUIDs in getCorrectedUsers() and return null from watchlist getters
- Rule comparator treats null as "unable to determine" and skips the comparison, preventing false deletions
PR #2444 - fix(ui): use ref-based guards in CollectionMediaPage to prevent duplicate fetches
- Replace state-based fetch guards with refs to avoid React batching issues
- Prevent duplicate API calls during pagination and collection media loading
PR #2444 - fix(ui): add fetchingRef guard in Overview to prevent concurrent page fetches
- Add ref-based guard to Overview component to prevent concurrent page fetches
- Mirrors the same pattern applied to CollectionMediaPage in previous commit
…, #2442, #2406, #2386, #2370

PR #2466 - fix: honor Jellyfin played threshold
- Respect configured played percentage threshold for Jellyfin watch status

PR #2461 - feat(rules): add ARR disk target path selection for disk space rules
- Allow selecting specific disk target paths for Radarr/Sonarr disk space rules

PR #2458 - feat: clean up empty ended shows in Sonarr after season actions
- Automatically remove ended shows from Sonarr when all seasons are processed

PR #2453 - fix: improve Plex viewCount reliability and add isWatched boolean
- Use native Plex viewCount field with watch history fallback
- Add new isWatched boolean rule property

PR #2452 - build(deps): bump actions/download-artifact from 7 to 8

PR #2451 - build(deps): bump actions/upload-artifact from 6 to 7

PR #2442 - fix(server): reject null/undefined in numeric rule comparisons
- Add getComparisonResult wrapper that fails closed on null/undefined operands
- Strict type checking for BIGGER/SMALLER comparisons

PR #2406 - Metadata provider abstraction layer with TVDB support
- Add MetadataService as central metadata resolution layer
- TVDB support as alternative metadata provider
- Dynamic provider preference with fallback
- Replace TmdbIdService with unified MetadataService

PR #2386 - feat: missing_episode rules
- Add missing episode count as a rule property for Sonarr

PR #2370 - build(deps-dev): bump the eslint group with 2 updates
PR #2442 - fix(server): reject null/undefined in numeric rule comparisons
- Treat missing last-view dates as stale
- Raise missing-operand rule logs to warn
- Normalize Plex provider rating slots
- Improve missing operand logging with watch state context
- Add isWatchDateRule helper to constants service
# Conflicts:
#	apps/server/package.json
#	apps/ui/package.json
#	yarn.lock
PR #2453 - fix: improve Plex viewCount reliability and add isWatched boolean
- Use native Plex viewCount field with watch history fallback
- Add new isWatched boolean rule property
- Share watched rule behavior across media servers
- Use uncached Plex history for watched rules
- Share Plex test fixtures
# Conflicts:
#	apps/server/package.json
PR #2488 - fix(server): improve Jellyfin watch data aggregation
- Improve watch history aggregation with per-user UserData lookups
- Honor Jellyfin played threshold (MaxResumePct) for watch status
- Add getItemFavoritedBy and getTotalPlayCount methods
- Address review feedback with debug logging and fallback handling
PR #2458 - feat: clean up empty Sonarr shows after season actions
- Move ServarrAction enum to @maintainerr/contracts for shared use
- Add SeerrApiService.hasRemainingSeasonRequests() for continuing show cleanup
- Update deleteShowIfEmpty to handle continuing shows via Seerr state check
- Exclude DELETE_SHOW_IF_EMPTY from Seerr request mutation in collection handler
- Use ServarrAction enum constants in UI AddModal instead of magic numbers
- Update force Seerr helper text to describe season-specific behavior
- Add SeerrApiModule to ActionsModule for DI
- Add comprehensive tests for all new Seerr-aware cleanup scenarios
PR #2492 - Fix Sonarr episode handling when media indexes are missing
- Safely identify episodes via getEpisodeLookup (index or air date fallback)
- Return boolean from action handlers to detect servarr update failures
- Add airDate-based episode matching in Sonarr helper
PR #2461 - feat(rules): add ARR disk target path selection for disk space rules
- Add Disk Target dropdown for Radarr/Sonarr disk space rules
- Support Aggregate (default) or specific Arr-reported path
- Fix Sonarr disk target (NFS/CIFS mounts not in /diskspace API)
- Merge /rootfolder data into diskspace results for remaining-space rules
- Move ArrDiskspaceResource + normalizeDiskPath to @maintainerr/contracts
- Extract evaluateArrDiskspaceGiB() to eliminate copy-paste between getters
- Add diskspace tests for both getter services
PR #2510 - fix: prevent crash when removing media from null Jellyfin collection
- Guard removeFromCollection API call when mediaServerId is null
- Move DB cleanup outside inner try block so items are always cleaned up
- Update parameter type to string | null for type accuracy
PR #2511 - fix: resolve Jellyfin collection creation failure
- Remove unnecessary re-fetch after creating a Jellyfin collection
- Switch getCollection to use direct getUserLibraryApi().getItem() lookup instead of query-based getItemsApi().getItems() pipeline
- Construct MediaCollection from known creation response data instead of re-querying
PR #2488 - fix(server): revert Jellyfin getItemUserData to use getItems endpoint
- Revert per-user data fetching from dedicated /UserItems/{itemId}/UserData endpoint back to getItems with enableUserData=true
- The dedicated endpoint does not reliably return per-user data with API key auth on all Jellyfin versions
- Fixes viewCount=0 and lastViewedAt=null regression reported in #2509
- Update test mocks to match reverted getItems call signature
PR #2453 - fix(server): fall back to native Plex viewCount for isWatched
- Use native Plex viewCount as fallback for isWatched boolean when watch history is empty
- Covers items marked watched without play events and purged history
- Numeric viewCount still uses history only to preserve server-wide accuracy
enoch85 added 4 commits March 21, 2026 21:06
PR #2494 - fix: added batch collection adding of media for Jellyfin
- Batch collection add/remove using Jellyfin's native multi-id API support
- New COLLECTION_CREATION_WITH_ITEMS feature flag to skip redundant resync
- Batch methods return failed IDs so the service layer only persists DB rows for items that actually landed on the server
- Create-with-items wired into createCollection: Jellyfin gets items at creation time, Plex and manual collections fall back to the sequential add path
- Plex sequential fallback with per-item error handling and 404 tolerance on remove
PR #2511 - fix: resolve Jellyfin collection creation failure
- avoid revalidating freshly reconciled collection links in the same rule run
- preserve stale-link healing for normal collection add/remove paths
- add focused rule executor coverage for Jellyfin resync and removal flows
@enoch85 enoch85 changed the title Fix/jellyfin stability hardening Finalize Jellyfin hardening for collections, search, and setup Mar 26, 2026
@enoch85

enoch85 commented Mar 26, 2026

Copy link
Copy Markdown
Collaborator Author

Can only be merged after the metadata PR.

Comment thread apps/server/src/modules/api/tmdb-api/tmdb.service.ts Dismissed
Comment thread packages/contracts/src/rules/arrDiskspaceResource.ts Dismissed
@enoch85 enoch85 changed the base branch from main to development March 31, 2026 20:43

enoch85 commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #2617, which carries forward the remaining valid hardening fixes from this branch against current development. The stale or broader feature work from #2534 was intentionally left out so the replacement PR stays narrowly scoped and reviewable.

@enoch85 enoch85 closed this Apr 7, 2026
@enoch85 enoch85 deleted the fix/jellyfin-stability-hardening branch April 26, 2026 18:50
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.

2 participants