Skip to content

Fix Sonarr series-title matching for ampersand vs "and" (e.g. "Pam & Tommy") - #86

Open
smoochy wants to merge 1 commit into
Vansmak:mainfrom
smoochy:claude/ampersand-title-match-clean
Open

Fix Sonarr series-title matching for ampersand vs "and" (e.g. "Pam & Tommy")#86
smoochy wants to merge 1 commit into
Vansmak:mainfrom
smoochy:claude/ampersand-title-match-clean

Conversation

@smoochy

@smoochy smoochy commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Bug

get_series_id() in media_processor.py failed to match "Pam & Tommy" against Sonarr's stored title "Pam and Tommy" (tvdbId 400690, Sonarr series id 181), even though the series exists, is monitored, and has episodes.

Root cause: the title-normalization helper _norm() stripped & entirely (via re.sub(r'[^\w\s]', '', s)) instead of equating it with and. So _norm("Pam & Tommy")"pam tommy" while _norm("Pam and Tommy")"pam and tommy" — not equal. Every matching tier failed:

  • Exact title match: fail (& vs and)
  • Year-suffix-stripped match: fail (no year suffix involved)
  • Alternate-title match: fail, _norm() doesn't equate &/and
  • Substring "close match" fallback: fail — "pam & tommy" is not a substring of "pam and tommy" or vice versa, so even the debug fallback logged "No close matches"

Why the TVDB-ID match (tier 1) didn't save this case

Sonarr does have the correct tvdbId (400690), and Plex's own metadata is correctly linked to the same TVDB entry — confirmed directly via the Plex API:

<Guid id="tvdb://400690" />

(from GET /library/metadata/935 on the live Plex server, item "Pam & Tommy")

However, Tautulli's {thetvdb_id} webhook template variable did not populate for this item across multiple playback events over several hours, despite Plex having correct metadata and the Tautulli notification agent's JSON body being configured correctly (verified against the user's actual live notification agent config):

{
  "plex_title": "{show_name}",
  "plex_movie_title": "{title}",
  "plex_season_num": "{season_num}",
  "plex_ep_num": "{episode_num}",
  "thetvdb_id": "{thetvdb_id}",
  "themoviedb_id": "{themoviedb_id}",
  "media_type": "{media_type}",
  "notification_type": "playback start"
}

This points to a Tautulli-side limitation/bug in resolving {thetvdb_id} for this item (out of scope for this repo), which means the title-matching fallback is not a rare edge case here — it's the path actually doing the work when Tautulli's ID fields come through empty.

Log evidence

episeerr (missing_logger, repeated across multiple playback events, 19:06–21:08):

2026-07-25 19:06:40,031 - INFO - Series not found in Sonarr: 'Pam & Tommy'. No close matches.
2026-07-25 19:06:40,031 - INFO - [Tautulli] Playback start for non-held series (Pam & Tommy S1E6) — ignored
2026-07-25 21:08:56,593 - INFO - Series not found in Sonarr: 'Pam & Tommy'. No close matches.
2026-07-25 21:08:56,593 - INFO - [Tautulli] Playback start for non-held series (Pam & Tommy S1E6) — ignored

Sonarr (GET /api/v3/series/lookup?term=Pam%20%26%20Tommy) confirms the series is present under a different title string:

{
  "title": "Pam and Tommy",
  "tvdbId": 400690,
  "tmdbId": 114925,
  "id": 181,
  "monitored": true,
  "statistics": {"seasonCount": 1, "episodeCount": 8}
}

Tautulli (docker logs, same playback sessions) shows the correct show name reaching Tautulli itself, confirming the data loss happens between Tautulli and its outbound webhook, not in Plex→Tautulli:

2026-07-25 19:06:39 - DEBUG :: Thread-17 (run) : Tautulli ActivityHandler :: Session 131 started by user ... with ratingKey 937 (Pam & Tommy - Pamela im Wunderland).

Plex (GET /library/metadata/935) confirms the show has correct TVDB linkage on the Plex side:

<Directory ratingKey="935" guid="plex://show/5fe0dd14733ce2002d89b695" title="Pam &amp; Tommy" originalTitle="Pam and Tommy" ...>
  <Guid id="imdb://tt13659418" />
  <Guid id="tmdb://114925" />
  <Guid id="tvdb://400690" />
</Directory>

Fix

Added a normalized-title comparison tier to get_series_id() that maps &and before stripping punctuation, checked as its own tier right after the year-suffix match (so it's tried before falling back to alternate titles), and reused the same normalization helper for the existing alternate-titles tier instead of duplicating logic:

def _norm(s):
    s = s.lower()
    s = s.replace('&', ' and ')
    s = re.sub(r'[^\w\s]', '', s)
    s = re.sub(r'\s+', ' ', s).strip()
    return s

webhook_norm = _norm(series_name)
for series in series_list:
    if _norm(series['title']) == webhook_norm:
        logger.info(f"Found normalized title match: '{series['title']}' matches '{series_name}'")
        return series['id']

Verification

Built the fixed image, deployed it to a live homelab instance running actual Sonarr/Tautulli integrations, and sent a Tautulli-style webhook POST with plex_title: "Pam & Tommy" (no thetvdb_id/themoviedb_id, matching the real-world failure case) against the live redeployed container. Confirmed:

  • New log line fires: Found normalized title match: 'Pam and Tommy' matches 'Pam & Tommy'
  • Old failure log line no longer fires: Series not found in Sonarr: 'Pam & Tommy'. No close matches.

🤖 Generated with Claude Code

get_series_id() previously stripped '&' to nothing in _norm() instead of
equating it with 'and', so a webhook title like 'Pam & Tommy' never matched
Sonarr's stored title 'Pam and Tommy'. Add a normalized-title tier (4.5)
that maps '&' -> ' and ' before comparing, applied to the primary title
match (not just the alternate-titles tier), and reuse that normalization
for tier 5's alternate-title comparison too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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