Skip to content

Keep remote watchlists working when Plex refuses the RSS feed URL - #199

Merged
Brandon-Haney merged 1 commit into
StudioNirin:mainfrom
Brandon-Haney:fix/plex-rss-endpoint-fallback
Jul 29, 2026
Merged

Keep remote watchlists working when Plex refuses the RSS feed URL#199
Brandon-Haney merged 1 commit into
StudioNirin:mainfrom
Brandon-Haney:fix/plex-rss-endpoint-fallback

Conversation

@Brandon-Haney

Copy link
Copy Markdown
Collaborator

What's happening

Plex serves watchlist RSS from rss.plex.tv/<uuid>. On 28 Jul 2026 that host started rejecting requests. My own instance went from clean fetches to failures inside a four hour window:

Run Result
Jul 25 00:30 → Jul 28 08:30 RSS feed contains 50 items, no errors
Jul 28 12:30 onward 3× 401, then cached data

What it returns now:

no token     -> 401  <Error error="Unauthorized" message="You must provide a token!" .../>
with token   -> 404  <Error error="Not Found" .../>

The 404 applies to every feed id, including invented ones, so it isn't specific to one account's feed. Everything else on the same token keeps working in the same run: user tokens load, OnDeck runs, and API based watchlists fetch normally.

The same feed is still reachable at discover.provider.plex.tv/rss/<uuid>, which redirects to a presigned copy of the identical document. I confirmed it is the same feed rather than a coincidence:

  • changing one character of the feed id returns 404, as does an invented id, so it is a keyed lookup and not a generic route
  • the redirect target is plex-rss-feeds.s3.../e/<feed-id>.xml
  • the response diffed against a locally cached copy taken from rss.plex.tv earlier the same morning: 50 items, 50 identical titles, no differences

What this PR does

Plex's settings page still generates rss.plex.tv URLs, so the configured URL stays primary and the other endpoint is only tried when Plex refuses it. If the primary host starts working again, runs go back to it with no config change and nothing to undo. The cache entry is keyed to the configured URL for the same reason.

Also in _fetch_rss_titles:

  • Sends X-Plex-Token, scoped to Plex hosts. remote_watchlist_rss_url is user supplied, so this keeps the token from being attached to an arbitrary domain. The redirect to storage is followed as a separate request without it, since the presigned URL carries its own credentials.
  • Stops retrying 401/403/404. Those answers are deterministic, so three attempts with backoff only delayed the fallback. Transient network errors still retry as before.
  • Logs the fallback at INFO and per attempt detail at DEBUG, so a fetch that recovers no longer reports as an error.

_load_rss_cache() returned cached items at any age. It still returns them, since losing every remote watchlist item would be worse, but past RSS_CACHE_STALE_HOURS (24h) it now warns that the feed has been unreachable and remote changes are not being picked up. This is what makes a prolonged outage visible rather than quietly serving old data.

At normal verbosity the whole thing is one line:

INFO - RSS: primary feed URL unavailable, served from Plex's discover endpoint instead

Scope

Affects any install with remote_watchlist_rss_url set. Installs that don't use remote watchlists are unaffected. No settings changes are required and no template or UI changes are included, since the URL Plex hands out is still the one to paste in.

Testing

28 new tests in tests/test_plex_rss_fallback.py covering URL derivation, token scoping, primary first ordering, no retry on auth failures, retained retries for transient errors, redirect handling, and the cache paths. Full suite passes.

To verify manually:

  1. With a remote watchlist RSS URL configured, run a cache operation and check the log. Remote watchlist items should be found, with the single INFO line above and no ERROR lines.
  2. Run with --verbose to see the ordering: primary attempted first, one 404, no retry, then the fallback.
  3. Point remote_watchlist_rss_url at a non Plex URL that 404s and confirm no fallback is attempted and no token is sent.
  4. Set the feed URL to something unreachable and confirm cached data is still used, with the staleness warning once the cache is over 24h old.

Plex serves watchlist RSS from rss.plex.tv/<uuid>. That host began returning
401 without a token and 404 with one, so remote watchlist data stopped
refreshing and runs fell back to increasingly old cached items. The same feed
stays reachable via discover.provider.plex.tv/rss/<uuid>, which redirects to a
presigned copy of the identical document.

Plex's own settings page still generates rss.plex.tv URLs, so the configured
URL remains primary and the mirror is only tried when Plex refuses it. If the
primary host starts working again, runs return to it with no config change.

Also in `_fetch_rss_titles`:

- Send X-Plex-Token, scoped to Plex hosts so a user-supplied feed URL can't
  send the token to an arbitrary domain. The redirect to storage is followed
  as a separate request without it, since the presigned URL carries its own
  credentials.
- Stop retrying 401/403/404. Those are deterministic, and three attempts with
  backoff only delayed the fallback.
- Log the fallback at INFO and the per-attempt detail at DEBUG. A recovered
  fetch is not an error and no longer reports as one.

`_load_rss_cache()` returned cached items at any age with no upper bound, which
is why the feed could go stale for days without it being obvious. It still
returns the data, because losing every remote watchlist item would be worse,
but past RSS_CACHE_STALE_HOURS it warns that the feed is unreachable and
changes are not being picked up.

Tests cover URL derivation, token scoping, primary-first ordering, no-retry on
auth failures, retained retries for transient errors, redirect handling, and
the cache paths.
@Brandon-Haney
Brandon-Haney merged commit 3e7bf29 into StudioNirin:main Jul 29, 2026
2 checks passed
@Brandon-Haney
Brandon-Haney deleted the fix/plex-rss-endpoint-fallback branch July 30, 2026 02:51
Brandon-Haney added a commit that referenced this pull request Jul 30, 2026
Two fixes to the image publishing workflow, both surfaced while bringing the
rolling :dev channel online in #200.

Docker v2 manifests. Images are pushed with OCI media types, and Unraid's
update check requests only Docker media types, so the registry returns 404 for
the manifest and the container reports "not available" rather than an update
status. Verified against the registry with Docker-only Accept headers:

  studionirin/plexcache-d:dev     -> 404  (application/vnd.oci.image.manifest.v1+json)
  studionirin/plexcache-d:latest  -> 200  (application/vnd.docker.distribution.manifest.v2+json)

`provenance: false` was already set and isn't sufficient on its own, so this
also disables SBOM attestation and sets oci-mediatypes=false via the image
exporter, which puts buildx back on a plain Docker v2 manifest. Pulling was
never affected — Docker handles OCI fine — so this only changes what the update
check can read. This matters most on a release: users whose update status reads
"not available" are never told a new version exists.

Cancel superseded builds. #199 and #200 merged 13 seconds apart. Both started a
build, and the older commit's run finished two seconds later, so it won the
:dev tag and left it pointing at a commit missing the newer merge. A
concurrency group keyed on the ref cancels the stale run so :dev always tracks
the newest push. Release builds use their own ref and are unaffected.

No change to which events publish or to the tags produced.
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