Skip to content

sw_lastWatched ("Newest episode view date") throws TypeError for shows with no view history #3083

@stormshaker

Description

@stormshaker

Describe the bug

The sw_lastWatched getter (rule field "Newest episode view date") returns the last-view date by reading watchHistory[0].viewedAt, guarded only by a truthy check on watchHistory. Because watchHistory is an array, an empty result ([]) is truthy, so for any show/season with no play history it takes the date branch and reads viewedAt off undefined, throwing TypeError: Cannot read properties of undefined (reading 'viewedAt').

The error is caught by the getter (logged as Plex-Getter - Action failed …), so it isn't fatal — but it has a real downstream effect (see Observed impact below).

To Reproduce

  1. Have a Plex library with at least one show that has no play history (never watched).
  2. Create a TV rule using "Newest episode view date" (e.g. is before — 180 days ago).
  3. Run the rule with debug logging enabled.
  4. A TypeError … reading 'viewedAt' is logged for every never-watched show evaluated.

Expected behavior

A show with no view history has no "newest episode view date", so the getter should return null (as its own : null fallback already intends) and the condition simply doesn't match — no exception, no caught error per item.

Device

  • OS: Linux (Docker)
  • Version: present on development @ v3.15.0 (confirmed by code inspection); trace below is from a deployment tracking development
  • Server: Plex

Debug logs

[DEBUG] [PlexGetterService] Plex-Getter - Action failed for '24' with id '3559': Cannot read properties of undefined (reading 'viewedAt')
TypeError: Cannot read properties of undefined (reading 'viewedAt')
    at PlexGetterService.get (.../rules/getter/plex-getter.service.js  // sw_lastWatched case)
    at async .../rules/helpers/rule.comparator.service.js:166:32
    at async Promise.all (index 3)
    at async RuleComparatorService.executeRule (.../rule.comparator.service.js:163:13)
    at async RuleComparatorService.executeRulesWithData (.../rule.comparator.service.js:102:21)
    at async RuleExecutorService.executeForRuleGroups (.../tasks/rule-executor.service.js)

Additional context

Root cause — apps/server/src/modules/rules/getter/plex-getter.service.ts, sw_lastWatched case (current development):

return watchHistory
  ? new Date(+watchHistory[0].viewedAt * 1000)
  : null;

For a never-watched show watchHistory is []; [] is truthy, so watchHistory[0] is undefined. A length guard (watchHistory?.length ? …) returns null as the existing : null branch already intends.

Observed impact (downstream of the throw)

The caught exception surfaces from the getter as undefined. The rule comparator treats a getter that yields undefined as a transient failure (intended for transport errors), and the executor preserves transient-failed items in their collection rather than removing them. The net effect: shows with no view history are never evaluated to a definitive result against a sw_lastWatched condition, so they can get stuck in a collection across runs even when they no longer match the rule.

Concrete example from a ~1,400-show Plex library: a "leaving soon" style rule had 152 never-watched shows sitting in its collection that no longer matched the rule. All 152 were confirmed to have zero history via Plex's own /status/sessions/history/all?metadataItemID=<id> endpoint. They persisted run after run purely because this getter threw (→ transient → preserved). With the getter returning null instead of throwing, the same run evaluated them honestly and the collection dropped to 0 — i.e. they had been phantom members the whole time. (Flagging since this is Plex-specific and may be hard to reproduce without a Plex server.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions