From 4b8ddad9ff59730705f8e7e93afe6c979abb16c9 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Mon, 29 Jun 2026 11:18:00 -0700 Subject: [PATCH] fix: stop the Tidal token-refresh loop (regression from #949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #949 moved the "token still valid -> return True" short-circuit in TidalClient.is_authenticated() into the boot-phase branch ONLY, so after boot every call fell through to the silent refresh regardless of whether the token was actually expired. With multiple workers polling Tidal every few seconds, that produced a constant "access token expired -> refresh -> success" loop (wolf39us logs) — needless token churn, not an actual auth problem. Restored the valid-token short-circuit on the post-boot path. The download client is unaffected (it defers the tidalapi session differently, no manual expiry loop). 3 regression tests: valid token post-boot does NOT refresh, expired token still does, valid token during boot returns True without probing. --- core/tidal_client.py | 6 ++++ tests/test_tidal_token_refresh_loop.py | 46 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 tests/test_tidal_token_refresh_loop.py diff --git a/core/tidal_client.py b/core/tidal_client.py index 49270403..41dc7605 100644 --- a/core/tidal_client.py +++ b/core/tidal_client.py @@ -529,6 +529,12 @@ class TidalClient: return True return bool(self.access_token and self.refresh_token) + # Token still valid — no refresh needed. (Restored: #949 moved this short-circuit + # into the boot-phase branch only, so every post-boot call fell through to the + # refresh below — a constant silent-refresh loop on a perfectly valid token.) + if self.access_token and time.time() < self.token_expires_at: + return True + # Backoff: if refresh recently failed, don't retry for 5 minutes if hasattr(self, '_refresh_failed_at') and self._refresh_failed_at: if time.time() - self._refresh_failed_at < 300: diff --git a/tests/test_tidal_token_refresh_loop.py b/tests/test_tidal_token_refresh_loop.py new file mode 100644 index 00000000..f62da188 --- /dev/null +++ b/tests/test_tidal_token_refresh_loop.py @@ -0,0 +1,46 @@ +"""Regression: post-boot, is_authenticated() must NOT refresh a still-valid Tidal token. + +#949 moved the "token still valid -> return True" short-circuit into the boot-phase branch +only, so every post-boot call fell through to the silent refresh — a constant-refresh loop +(wolf's logs: "access token expired -> refresh -> success" every few seconds).""" + +import time + +import core.boot_phase as boot_phase +from core.tidal_client import TidalClient + + +def _client(expires_at): + c = TidalClient.__new__(TidalClient) + c.access_token = "tok" + c.refresh_token = "refresh" + c.token_expires_at = expires_at + c._refresh_calls = 0 + + def _fake_refresh(): + c._refresh_calls += 1 + return True + + c._refresh_access_token = _fake_refresh + return c + + +def test_valid_token_does_not_refresh_post_boot(monkeypatch): + monkeypatch.setattr(boot_phase, "_boot_active", False) # post-boot + c = _client(time.time() + 3600) # valid for an hour + assert c.is_authenticated() is True + assert c._refresh_calls == 0 # MUST NOT refresh a valid token + + +def test_expired_token_still_refreshes_post_boot(monkeypatch): + monkeypatch.setattr(boot_phase, "_boot_active", False) + c = _client(time.time() - 10) # expired + assert c.is_authenticated() is True + assert c._refresh_calls == 1 # expired -> one refresh + + +def test_valid_token_returns_true_during_boot(monkeypatch): + monkeypatch.setattr(boot_phase, "_boot_active", True) # boot phase + c = _client(time.time() + 3600) + assert c.is_authenticated() is True + assert c._refresh_calls == 0 # boot never probes/refreshes