Fix realtime resubscribe using an expired access token + bounded teardown - #435
Merged
Danielhiversen merged 2 commits intoJul 27, 2026
Merged
Conversation
Two related realtime bugs, both reported against Home Assistant (home-assistant/core#176268, home-assistant/core#168480): 1. The delayed resubscribe scheduled after a TransportError called rt_resubscribe(), which re-authenticates with the cached access token. When the token had expired in the meantime (Home Assistant OAuth tokens live ~1 hour and typically expire while the websocket is up), the resubscribe failed with InvalidLoginError ('"exp" claim timestamp check failed'), was logged once and abandoned - leaving the home permanently unsubscribed. It now goes through TibberRT.reconnect() - the token-refreshing path - with bounded retries and exponential backoff, and detaches itself before reconnecting so the connection-wide resubscribe cannot cancel its own task mid-flight. 2. reconnect() only rebuilt the subscription manager when the refreshed token string differed from the cached one. The token is baked into the transport's init_payload at construction, so an expired-but-identical token was silently reused and the reconnect loop never recovered. The rebuild now happens unconditionally after a token refresh. Also hardens teardown, which could block forever on a half-dead socket (wedging Home Assistant config-entry unloads into FAILED_UNLOAD): close paths are bounded (10 s transport close, 15 s sub_manager close) and the connect lock is per-instance instead of module-global. Verified in production on a Home Assistant OS instance for 48+ hours, including ~48 hourly token-rotation reconnect cycles and forced TCP socket kills, with no stuck connections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DERobfZZh3npFRbwpdxzz2
honzup
marked this pull request as ready for review
July 16, 2026 07:56
21 tasks
Danielhiversen
approved these changes
Jul 27, 2026
|
Field confirmation from production (pyTibber 0.37.6 via Home Assistant on NixOS), 2026-07-28:
This PR looks like it fixes precisely the behavior above. A tagged release would be much appreciated so it can reach Home Assistant and downstream distros. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two related realtime bugs reported against Home Assistant (home-assistant/core#176268, home-assistant/core#168480), plus teardown hangs that wedge HA config-entry unloads into
FAILED_UNLOAD.Bug 1: delayed resubscribe uses the cached (expired) token
After a
TransportError,TibberHome._delayed_resubscribecalledrt_resubscribe(), which re-authenticates with the cached access token. HA's OAuth tokens live ~1 hour and usually expire while the websocket is up, so the resubscribe failed withInvalidLoginError: "exp" claim timestamp check failed— and was logged once and abandoned (no retry, andrt_unsubscribe()had already torn the listener down). This is the endlessWatchdog: Connection is downloop in core#176268.Fix:
_delayed_resubscribenow goes throughTibberRT.reconnect()— the only token-refreshing path — with bounded retries (5 attempts, exponential backoff). It detaches itself before reconnecting so the connection-wide resubscribe can't cancel its own task mid-flight, and early-returns if another path already restored the subscription.Bug 2: reconnect() silently reuses an expired token
reconnect()only rebuilt the sub_manager when the refreshed token string differed from the cached one — but the token is baked into the transport'sinit_payloadat construction, so an expired-but-identical token was reused forever. The rebuild now runs unconditionally after a refresh.Teardown hardening
TibberWebsocketsTransport.close():wait_closed()bounded to 10 s (a half-dead socket previously blocked forever — this is what deadlocks HA's config-entry unload intoFAILED_UNLOAD).sub_manager.close_async()bounded to 15 s in_reset_connection_locked.Testing
ruff check/ruff formatclean,mypyclean.