Report a refused or unreachable cast instead of hanging on it - #71
Merged
Merged
Conversation
A cold cast to a Chromecast could hang indefinitely: Home Assistant does not answer a service call until the service returns, so the caller waited with nothing to time out against. Observed as a service call still unanswered after 150 seconds, while the device sat untouched. Four things combined to produce that, and each is a problem on its own. launch_app waited on Chromecast.wait() twice with no timeout. When the socket client cannot reach the device it retries forever, so an unreachable device blocked the call rather than failing it. The wait is now bounded and reports what happened. The wait loop only ever checked is_launched. A device that had already replied refusing the credentials was polled over until max_attempts ran out, then reported as "Timeout when waiting for status response from Spotify app" -- a wait for an answer that had arrived and said no. _add_user_error_handler and _transfer_error_handler raised. They run on the socket client's message-routing thread, where the exception is logged as "Error doing job: AppLaunchError exception in shielded future" and discarded, so it never reached the caller. They now record the failure and let the waiting thread raise it. Both handlers took *_, **__ and dropped the payload, which is the only account of why Spotify refused: status, statusString and spotifyError. A refusal is now reported as "Spotify refused the credentials for this device (status=108, statusString=ERROR-CANNOT-LOAD, spotifyError=409)" rather than a fixed string, which is the difference between knowing to re-authorize and guessing. Verified against a live device that had been failing: the same cast returned in 9.5 seconds and started playback, where it had previously not answered at all.
…aller Follow-up to the cherry-picked fix from #67: - `Chromecast.wait(timeout=...)` raises `RequestTimeout` rather than returning, so the bounded wait is wrapped in `wait_for_connection` and re-raised as `AppLaunchError`, which Home Assistant renders. - The first unbounded wait was in `async_build_from_type`, before `launch_app` ever ran; it now uses the same bounded helper. - Dropped the `socket_client.is_connected` guard, which only reflects `not connecting` and fires transiently during reconnects. - Both error handlers record `launch_error` before setting `credential_error`, since the waiting thread polls the flag and would otherwise fall back to the generic message. - CHANGELOG entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ride Home Assistant pins PyJWT 2.13.0 itself from 2026.8.0, so the uv override that forced it in the dev environment is no longer needed. `uv lock --upgrade` moves the dev lock from HA 2026.7.2 to 2026.9.1 (pylint 4.0.8, coverage 7.16.0, RapidFuzz 3.14.6 and friends). A pip-audit over the locked environment reports no known vulnerabilities. Also applies the one pylint refactor hint left in `rate_limit.py` (`max()` instead of an `if` block), which is behaviour-preserving. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 7, 2026
Merged
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.
Description
Supersedes #67 (author unresponsive since the review). The first commit is @chsienki's commit from that PR, cherry-picked as-is for credit. The second commit applies the changes requested in the #67 review.
A cold cast to a Chromecast could hang the
spotcast.playservice call indefinitely, and a device that refused the Spotify credentials produced only a generic timeout. Four defects, all verified against the vendored pychromecast 14.0.10 source:Chromecast.wait(), both inasync_build_from_type(the first and actually blocking one,media_player/utils.py) and twice inlaunch_app. All three now go throughwait_for_connection, bounded byCONNECT_TIMEOUT(20s).Chromecast.wait(timeout=...)raisesRequestTimeout(aPyChromecastError) rather than returning, so the helper wraps it and re-raisesAppLaunchError, which Home Assistant renders as a proper error.is_launched, so a refusal that had already arrived was polled over untilmax_attemptsran out. The loop now checkscredential_error, and both fields are reset at the top oflaunch_app._add_user_error_handlerand_transfer_error_handlerraised on pychromecast's socket thread, where_route_messagecatches and logs the exception (socket_client.py:735). They now record the failure and let the waiting thread raise it.launch_erroris written beforecredential_errorbecause the waiting thread polls the flag every second and would otherwise read the generic fallback.status,statusString,spotifyErrorandreason.Relative to the #67 diff, this drops the
socket_client.is_connectedguard (it is literallynot connectingand flips during every reconnect attempt) and the mock-only "unreachable device" test that passed on a path production never takes.Type of Change
No service, entity or WebSocket shape changes. CHANGELOG updated under Unreleased.
Checklist
Verification
pylint --fail-under 9 custom_components: 10.00/10.RequestTimeout, pin the assignment order in both handlers, and pin theutils.pycall so it cannot revert to a baremedia_player.wait.🤖 Generated with Claude Code