From 6b5506dee436f9c1469543ab766799433061ccd1 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Fri, 5 Jun 2026 22:38:59 -0700 Subject: [PATCH] Don't apply the real-API daily budget / cooldown while bridging via Spotify Free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #798 follow-up. The Spotify enrichment worker's daily budget and post-ban cooldown both exist to protect the REAL authenticated API from bans. But the worker bridges to the no-creds Spotify Free source during a ban (a different, anonymous path), and those guards weren't free-aware: - the budget guard slept the worker when the daily cap was hit, blocking free work for no reason, - free-served items still incremented the budget counter, draining the real-API cap with calls that never touched the real API (so you'd return from a ban with budget already spent), and - the post-ban cooldown slept the worker even when serving via free. Fix: compute free_serving = client._free_active() once per loop (defensively wrapped → False on error → original behavior). When free_serving: - skip the daily-budget guard, - skip the post-ban-cooldown guard, - don't increment the daily budget. So the budget is now strictly a cap on REAL Spotify API usage; free runs unthrottled by it (the free client keeps its own inter-call pacing). The decision input (_free_active) is already pinned by the gate-model tests (free True exactly when rate-limited+Free / Free-primary, False when authed+healthy). Full suite clean (only pre-existing soundcloud /app env failures remain). --- core/spotify_worker.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/core/spotify_worker.py b/core/spotify_worker.py index 966d964f..95060d97 100644 --- a/core/spotify_worker.py +++ b/core/spotify_worker.py @@ -209,8 +209,19 @@ class SpotifyWorker: interruptible_sleep(self._stop_event, min(remaining, 60)) # Check again every 60s max continue - # Daily budget guard — worker-only cap to avoid saturating Spotify rate limits - if self._is_daily_budget_exhausted(): + # Is the worker serving via the no-creds Spotify Free source this + # iteration? The daily budget and post-ban cooldown both exist to + # protect the REAL authenticated API from bans — they don't apply + # to free (a different, anonymous path). Computed once and reused + # below; the loop already probes auth, so no extra quota cost. + try: + free_serving = self.client._free_active() + except Exception: + free_serving = False + + # Daily budget guard — worker-only cap to avoid saturating the REAL + # Spotify API. Skipped while serving via free (free isn't that API). + if not free_serving and self._is_daily_budget_exhausted(): budget = self._get_daily_budget_info() resets_in = budget['resets_in_seconds'] logger.info(f"Daily enrichment budget exhausted ({budget['used']}/{budget['limit']}), " @@ -219,8 +230,9 @@ class SpotifyWorker: continue # Post-ban cooldown guard — after ban expires, wait before resuming - # to avoid immediately re-triggering the rate limit - cooldown = self.client.get_post_ban_cooldown_remaining() + # to avoid immediately re-triggering the rate limit. Only matters + # for the real API, so skip it while serving via free. + cooldown = 0 if free_serving else self.client.get_post_ban_cooldown_remaining() if cooldown > 0: logger.debug(f"Post-ban cooldown active ({cooldown}s left), sleeping...") interruptible_sleep(self._stop_event, min(cooldown, 60)) @@ -261,7 +273,10 @@ class SpotifyWorker: continue self._process_item(item) - self._increment_daily_budget() + # Only real-API work counts toward the daily cap — free-served + # items don't touch the authenticated API's quota. + if not free_serving: + self._increment_daily_budget() interruptible_sleep(self._stop_event, self.inter_item_sleep) except SpotifyRateLimitError: