Files
docker-infrastructure/.claude/skills/sonarr/SKILL.md
T
poprhythm 34a45cba76 Add sonarr.sh + skill for TV library manual-import migration
claude-homelab's Sonarr skill only covers search/add/remove, not the
manual-import workflow the Jellyfin migration depends on. Wraps
lookup/add/scan/import into a script matching this repo's
portainer.sh conventions, replacing repetitive raw curl calls.

Also documents a naming-token gotcha hit while migrating the first
two pilot shows: Sonarr's series folder format needs the combined
{Series TitleYear} token, not {Series Title} ({Year}) - the latter
silently drops the year.

Claude-Session: https://claude.ai/code/session_01HZQK6jHmdTpFjFZM8FUnqA
2026-09-08 02:38:48 +00:00

5.5 KiB

name, description
name description
sonarr Use Sonarr to add TV shows and manually import existing raw-named folders into the Show Name (Year)/Season NN/... layout Jellyfin and Plex expect. Use when migrating the TV library, importing a newly-downloaded show, or otherwise interacting with this repo's Sonarr instance.

Sonarr library migration/import

This repo's Sonarr manages TV show organization for both Jellyfin and Plex (they share the same nas_media library at /data/video/tv). The claude-homelab plugin's Sonarr skill (if installed) only covers search/add/remove — it has no manual-import support. This skill's sonarr.sh covers that gap: matching an existing raw-named folder to the right show/episodes and importing it into place.

Always use ./sonarr.sh (repo root) for this — never hand-roll the Sonarr API calls, the folder-naming token gotcha below has already bitten this workflow once.

Workflow: migrate one existing show

  1. Find the TVDB match: ./sonarr.sh lookup "<show name>" — prints candidates as tvdb:<id> Title (Year) status. Pick the right one (check year/status against what's actually in the folder).
  2. Add it: ./sonarr.sh add tvdb:<id> — adds unmonitored, no search, root folder defaults to /data/video/tv. Skip if already in ./sonarr.sh list.
  3. Preview the import: ./sonarr.sh scan "<raw folder path>" — shows how each file will map to season/episode, and flags anything Sonarr couldn't parse (!! <rejection reason>). Read this before importing — don't skip straight to step 4.
  4. Apply it: ./sonarr.sh import "<raw folder path>" — re-scans and imports only if every file matched cleanly (refuses and tells you to check scan output otherwise, rather than partially importing). Files are hardlinked (not copied) into /data/video/tv/Show Name (Year)/Season NN/Show Name - SxxExx - Title Quality.ext — no extra disk usage, original folder is left behind but empty.
  5. Clean up the empty source folder: docker exec jellyfin rmdir "<raw folder path>" (any container with the nas_media mount works).
  6. Verify Plex picks it up correctly — this is not automatic, see below.

Verifying the Plex side (do this every time, not just once)

Moving a file changes its path. Plex's database still points at the old path until it rescans — it does not watch the filesystem live here. After every import:

TOKEN=$(docker exec plex grep -o 'PlexOnlineToken="[^"]*"' \
  "/config/Library/Application Support/Plex Media Server/Preferences.xml" | cut -d'"' -f2)
curl -s "http://localhost:32400/library/sections/2/refresh?X-Plex-Token=$TOKEN"

(Section 2 is "TV Shows" in this Plex instance — confirm with curl -s "http://localhost:32400/library/sections?X-Plex-Token=$TOKEN" if unsure.) Then spot-check that the show's episodes still show the correct viewCount/watch state pointing at the new file path — Plex re-matches by its own metadata agent (title/GUID), not by the old path, so watched status normally survives a move, but confirm it rather than assuming, especially for messier shows (duplicate releases, specials, date-based naming).

Known gotcha: {Year} is not a valid Sonarr token

The series folder format must use the combined token {Series TitleYear} (produces Title (Year) as one unit) — {Series Title} ({Year}) silently produces Title () with no year, because Sonarr has no standalone {Year} token for series-level naming. This bit the first two shows migrated (Andor, 3 Body Problem) before being caught — check docker exec sonarr curl -s http://localhost:8989/api/v3/config/naming/examples -H "X-Api-Key: $SONARR_API_KEY" (or curl "$SONARR_URL/api/v3/config/naming/examples" -H "X-Api-Key: $SONARR_API_KEY" from the host) any time naming config changes — seriesFolderExample should show a real year, not ().

Edge cases that will need manual handling (not automatable via scan/import)

  • Date-based shows (e.g. Jeopardy) — no season/episode numbers in filenames. Set the show's Series Type to "Daily" in the Sonarr UI before scanning; scan will otherwise reject every file.
  • Specials mixed into a season folder (e.g. Lower Decks S00E501-style files) — Sonarr should route these to Season 00 correctly; verify rather than assume.
  • Duplicate episodes from two releases (e.g. Poker Face) — decide which release to keep before importing; scan will show both, import will refuse until resolved since it won't guess.
  • Combined/ambiguous episode files (e.g. e01-02. with no season number) — scan will likely reject these; use the Sonarr web UI's Manual Import screen instead, which allows manually assigning an episode range per file (the CLI script only handles clean auto-matches by design, so it can't silently mis-import something ambiguous).

Other commands

  • ./sonarr.sh list — series currently in Sonarr
  • ./sonarr.sh rootfolders — root folders + how many unmapped (not-yet-added) folders each has
  • ./sonarr.sh queue — current download queue
  • ./sonarr.sh test-client — tests configured download client connections (currently just qBittorrent_vpn, category tv-sonarr)

Credentials

SONARR_API_KEY / SONARR_URL live in this repo's .credentials (source it before running sonarr.sh manually outside the script — the script sources it itself). Get a fresh key from Sonarr UI → Settings → General → Security, or docker exec sonarr grep -o 'ApiKey>[^<]*' /config/config.xml.