From 1d5f1e204773e54e1cc38913e7dda9d1015e7add Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:00:49 -0700 Subject: [PATCH] fix: pause Spotify worker on non-Spotify primary + cut daily budget to 500 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Spotify enrichment worker was auto-starting unconditionally at boot, hammering /v1/search to match every track in the library against the Spotify catalog regardless of which metadata source the user had actually chosen as their primary. Users on Deezer, iTunes, Discogs, or Hydrabase saw multi-hour 429 bans (typically 14400s) on Spotify even though they never wanted Spotify-driven enrichment in the first place — the worker generated dead API traffic the user neither asked for nor benefited from. Compounded by Spotify's February 2026 API tightening: - /v1/search max limit cut from 50 to 10 per request, default from 20 to 5 — every track now needs more pagination, more requests. - Sustained-rate detection more aggressive — repeated calls over hours trigger automated long-form bans even when each individual 30-second window is well under the rolling limit. Result: a user on Deezer would see their Spotify connection get banned for 4 hours after about 30 tracks of enrichment activity, with no recourse other than manually pausing the worker each session. Two-part fix: 1. Boot gate (web_server.py): only auto-start the worker when `get_primary_source() == 'spotify'`. Otherwise initialize in the paused state with an explanatory log line. The settings UI manual unpause control remains functional for users who explicitly want background Spotify enrichment regardless of primary source. Boot logic: - User manually paused (existing config) → stays paused (preserved). - Primary = 'spotify' → starts running (preserved). - Primary != 'spotify' → starts paused with log line. 2. Daily budget reduction (core/spotify_worker.py): drop from 3000 to 500 items per calendar day. The 3000 cap was set when /v1/search returned 50 results per call; now that it caps at 10, each track needs roughly 5x the API load to find a confident match. 500/day keeps the worker productive without crossing Spotify's hidden sustained-rate detection threshold. The runtime side of the boot gate — auto-pausing when the user switches primary source mid-session — is out of scope. The settings UI already exposes the manual toggle, and primary-source switches are infrequent enough that requiring a manual unpause after the fact is acceptable. Full suite: 1355 passing. Ruff clean. --- core/spotify_worker.py | 9 +++++++-- web_server.py | 21 ++++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/core/spotify_worker.py b/core/spotify_worker.py index dde78781..a3e9cd89 100644 --- a/core/spotify_worker.py +++ b/core/spotify_worker.py @@ -55,8 +55,13 @@ class SpotifyWorker: self.inter_item_sleep = 1.5 # Between top-level items (each can trigger 5+ paginated calls) self.batch_inter_item_sleep = 0.1 # Between local matches within a batch (no API calls) - # Daily budget — caps how many items this worker processes per calendar day - self.daily_budget = 3000 + # Daily budget — caps how many items this worker processes per calendar day. + # Lowered from 3000 to 500 after Spotify's February 2026 API tightening + # (/v1/search max limit cut from 50 to 10) increased the per-track API call + # cost. Sustained 3000-item runs were tripping Spotify's automated abuse + # detection and earning multi-hour 429 bans. 500/day keeps the worker + # productive without crossing the threshold. + self.daily_budget = 500 self._daily_items_processed = 0 self._daily_date = date.today() diff --git a/web_server.py b/web_server.py index 4364b4b5..fc195926 100644 --- a/web_server.py +++ b/web_server.py @@ -37103,18 +37103,33 @@ def deezer_resume(): # ================================================================================================ # --- Spotify Worker Initialization --- +# The Spotify enrichment worker calls `/v1/search` continuously to match library +# tracks against Spotify's catalog. After Spotify's February 2026 API tightening +# (search limit cut from 50→10, sustained-rate detection more aggressive), running +# this worker when the user has chosen a non-Spotify primary metadata source +# (Deezer, iTunes, Discogs, Hydrabase) generates dead API traffic that triggers +# multi-hour 429 bans and disrupts the user's actual selected source. +# +# Gate the worker at boot: only auto-start when Spotify is the configured primary +# source. Users on other sources can manually unpause the worker from settings if +# they explicitly want background Spotify enrichment. spotify_enrichment_worker = None try: + from core.metadata_service import get_primary_source as _get_primary_source from database.music_database import MusicDatabase spotify_enrichment_db = MusicDatabase() spotify_enrichment_worker = SpotifyWorker(database=spotify_enrichment_db) - if config_manager.get('spotify_enrichment_paused', False): + _primary = _get_primary_source() + _user_paused = config_manager.get('spotify_enrichment_paused', False) + if _user_paused or _primary != 'spotify': spotify_enrichment_worker.paused = True # Set BEFORE start() to prevent race condition spotify_enrichment_worker.start() - if spotify_enrichment_worker.paused: + if not spotify_enrichment_worker.paused: + logger.info("Spotify enrichment worker initialized and started") + elif _user_paused: logger.info("Spotify enrichment worker initialized (paused — restored from config)") else: - logger.info("Spotify enrichment worker initialized and started") + logger.info(f"Spotify enrichment worker initialized (paused — primary metadata source is '{_primary}', not Spotify)") except Exception as e: logger.error(f"Spotify enrichment worker initialization failed: {e}") spotify_enrichment_worker = None