Changelog
June 16, 2026 ยท View on GitHub
[Unreleased]
Added
EXCLUDE_LABELSenvironment variable (default empty): comma-separated list of Plex labels that mark items as opted-out of labelarr. Items carrying any of these labels are skipped during both apply and removal passes. Case-insensitive; surrounding whitespace and empty values in the CSV are ignored. Logged at startup when active ([INFO] EXCLUDE_LABELS active - items tagged with any of [...] will be skipped) and per skipped item underVERBOSE_LOGGING=true.
Documentation
- README
Library Selectionsection now documentsMOVIE_LIBRARY_EXCLUDEandTV_LIBRARY_EXCLUDE, which were added in 1.3.0 but only appeared in the changelog.
[1.3.2] - 2026-04-21
Security
- Plex client error messages now redact
X-Plex-Token,apikey, andapi_keyquery-string values. Previously a transport failure (e.g. TLS cert rejection) would bubble up Go's*url.Error, which embeds the full request URL including the Plex token, leaking it into logs/Loki. Allc.httpClient.Docalls are now routed through asafeDowrapper that scrubs the error string before returning.
[1.3.1] - 2026-04-21
Security
- Plex TLS certificate verification is now enabled by default. Previously,
InsecureSkipVerifywas hardcoded totrue, which silently trusted any server certificate. SetPLEX_INSECURE_SKIP_VERIFY=trueto opt back into skip-verify (e.g., for self-signed Plex certs). A[WARN]line is logged at startup when the flag is on. Resolves CodeQLgo/disabled-certificate-check. - Export paths are now validated to stay within
EXPORT_LOCATION. Library names that resolve to.,.., or empty after sanitization are rejected, and asafeJoinhelper verifies every export path stays inside the export root before writing. Resolves CodeQLgo/path-injection. .github/workflows/release.ymlnow declares a least-privilege top-levelpermissions: contents: read. Per-job elevated permissions oncreate-release,build-binaries, andpublish-dockerare unchanged. Resolves CodeQLactions/missing-workflow-permissions.
Added
PLEX_INSECURE_SKIP_VERIFYenvironment variable (defaultfalse).
Changed
- Bumped Docker build base images to
golang:1.26-alpine(builder) andalpine:3.22(runtime) to pick up patched Go stdlib and Alpine packages. Clears 1 CRITICAL + 8 HIGH CVEs surfaced by Trivy on the previousgolang:1.23-alpine/alpine:3.21base (CVE-2025-68121, CVE-2025-58183, CVE-2025-61726/28/29, CVE-2026-25679/32280/32281/32283). go.modnow requires Go 1.26;release.ymlactions/setup-gopinned to1.26..dockerignorehardened to exclude.env*,*.pem,*.key,.claude,.github,CLAUDE.md,PR_DESCRIPTION.md,tmp/.
[1.3.0] - 2026-04-12
Added
POST /scanendpoint on the webhook server for manual scan triggers.POST /scanruns a full scan across all non-excluded libraries.POST /scan?library=<id|name>scans a single library;libraryaccepts the numeric Plex section ID or a case-insensitive library title.- Returns
202 Acceptedimmediately; the scan runs in the background. Returns409 Conflictif a scan is already in progress,404 Not Foundif the library param does not match, and405 Method Not Allowedfor non-POST requests. - Works in
WEBHOOK_ONLY=truemode โ enables ad-hoc catchup scans without toggling environment variables.
[1.2.3] - 2026-04-12
Fixed
- Every Plex webhook delivery was being rejected with
HTTP 400becausePlexWebhookPayload.Metadata.GUIDwas typed asstring, while Plex sends it as an array of objects ([{id:"imdb://..."}, ...]) for multi-provider items.json.Unmarshalfailed before reaching the event handler. The unused field has been removed; unknown JSON fields are ignored by the decoder, so the shape no longer matters.
[1.2.2] - 2026-04-12
Changed
- Webhook 400 responses now log the specific failure reason (parse error,
missing payload field, or JSON unmarshal error) along with
Content-Type, form/file part keys, and a payload snippet. Previously all three paths returned 400 with no log, making Plex delivery failures invisible.
[1.2.1] - 2026-04-12
Added
MOVIE_LIBRARY_EXCLUDE/TV_LIBRARY_EXCLUDE: comma-separated library IDs to skip when*_PROCESS_ALL=true. Excluded libraries are filtered from both timer-driven processing and webhook routing.WEBHOOK_ONLY=true: skips the startup full scan and the periodic timer entirely, leaving the webhook server as the only trigger. RequiresWEBHOOK_ENABLED=true.
Fixed
- Webhook items are no longer silently dropped when a full library scan is
in progress.
ProcessSingleItemnow waits for the per-library slot to free up (polling every 5s, bounded to a 2-hour deadline) instead of logging a fake "queuing for next cycle" and returning early.
[1.2.0] - 2026-04-10
Added
Plex Webhook Support
- Webhook listener for real-time processing (WEBHOOK_ENABLED, WEBHOOK_PORT)
- Handles library.new events (other Plex events are accepted but ignored to avoid reprocessing items that don't need it)
- Configurable debounce window (WEBHOOK_DEBOUNCE, default 30s)
- Prevents concurrent processing of the same library
- Health check endpoint at /health
- Runs alongside the existing timer
Keyword Prefix
- KEYWORD_PREFIX env var to prepend text to keywords (e.g. "- ")
- Useful when UPDATE_FIELD=genre to separate TMDb keywords from real genres
Batch Processing
- BATCH_SIZE (default 100) and BATCH_DELAY (default 10s) env vars
- Prevents API flooding on large libraries (4000+ items)
- ITEM_DELAY (default 500ms) controls per-item pacing
Version Tracking
- Version constant in internal/version/version.go
- Logs version on startup
Changed
- Removed all emoji from log output; replaced with bracketed tags
- Extracted Clients struct for processor initialization
- Added keyword cache by TMDb ID to avoid redundant API calls
- Eliminated redundant Plex API call after keyword sync for export
2025-07-05
Added
Radarr/Sonarr Integration
- Radarr API client (internal/radarr/) -- movie lookup by title, year, TMDb ID, IMDb ID, file path
- Sonarr API client (internal/sonarr/) -- series lookup by title, year, TMDb ID, TVDb ID, IMDb ID, file path
- USE_RADARR, USE_SONARR, RADARR_URL, RADARR_API_KEY, SONARR_URL, SONARR_API_KEY env vars
- TMDb ID extraction chain: Plex metadata -> Radarr/Sonarr -> file path regex
- Connection testing on startup for all enabled services
Verbose Logging
- VERBOSE_LOGGING env var (default false)
- Shows TMDb ID lookup source, Plex GUIDs, matching attempts
- Progress percentage for libraries over 100 items
Persistent Storage
- JSON file storage for processed items (DATA_DIR env var)
- Tracks rating key, TMDb ID, update field, last processed time
- Skips already-processed items unless FORCE_UPDATE=true
- Runs in ephemeral mode when DATA_DIR is not set
Keyword Normalization
- Pattern-based normalization: sci-fi -> Sci-Fi, romcom -> Romantic Comedy
- Acronym detection (FBI, CIA, DEA, etc.)
- Century formatting (5th century bc -> 5th Century BC)
- City/state, relationship, and credit stinger patterns
- 90+ test cases
- Duplicate cleaning: removes old unnormalized keywords when normalized versions are added
Force Update Mode
- FORCE_UPDATE env var (default false)
- Reprocesses all items regardless of storage state
Export Functionality
- EXPORT_LABELS, EXPORT_LOCATION, EXPORT_MODE (txt/json) env vars
- Generates file lists per label per library
- JSON mode outputs a single structured export.json
- TXT mode creates per-library subdirectories with summary.txt
Changed
- NewProcessor accepts optional Radarr/Sonarr clients and returns error
- TMDb client normalizes keywords before returning them
- Removal delay reduced from 500ms to configurable ITEM_DELAY
Technical Notes
- Radarr/Sonarr use API v3
- All new features are optional and backward compatible
- No breaking changes to existing configuration