Conversation
WebSocketServer.handle_message returned None for a message whose
destination had no registered connection, so the sender never heard
anything: a GUI request for a service that was still starting, or whose
session the daemon had just closed after missed heartbeats, sat until the
client's own timeout (ten minutes in the GUI) with nothing to show why.
Reply to such requests with {"success": False, "error": "<service> is
not connected to the daemon"} addressed to the sender. Replies routed to
a client that has gone (ack messages) and state change broadcasts to
wallet_ui while no UI is connected still get no answer: nothing waits
for those, and answering every broadcast on a headless node would only
make noise.
The GUI already copes with the answer: its service start-up wait pings
the service through the daemon and retries after a second on any
failure, so start-up is unchanged, and an ordinary request now fails
immediately with a message instead of hanging.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TzNNe89x7dm2caSxLWXjZY
…very broadcast silent remove_connection() leaves an empty set behind under the service's name, so `destination in self.connections` stayed true after the daemon closed a session and the request was forwarded to no socket at all, which is the very case the reply is for. Forward only when the destination has a live socket. Services broadcast state changes to metrics and unfinished_block_info as well as to wallet_ui. Name the three in one place so a broadcast to any subscriber that is not connected keeps getting no answer, instead of an error back to the producing service on every event. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TzNNe89x7dm2caSxLWXjZY
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit efe2e71. Configure here.
emlowe
approved these changes
Sep 15, 2026
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.
Purpose:
Make a request for a service that is not connected to the daemon fail visibly instead of silently.
WebSocketServer.handle_messagereturnedNonewhen a message's destination had no registered connection, so the sender got nothing at all. In the GUI that means a spinner until its own ten-minute client timeout, with no hint of what went wrong.This surfaced while diagnosing a GUI that was unusable after every restart on a large wallet (see #21392 for the root cause and the fix on the service side): once the daemon had closed the wallet's session after missed heartbeats, every command the GUI sent to
chia_walletwas discarded here without a reply, and the key-selection screen just spun.Current Behavior:
A message addressed to a service with no registered daemon connection is dropped. The sender never learns that, and waits for its own timeout.
New Behavior:
A request addressed to a service with no live connection (never registered, or registered and since closed by the daemon, which leaves an empty set behind under its name) is answered, to the sender only, with
{"success": false, "error": "<service> is not connected to the daemon"}in the usual response shape (samerequest_id,ack: true), so the client can retry or report it right away.Two kinds of messages are deliberately still dropped without a reply, because nothing waits for them: replies routed to a client that has gone (
ack: truemessages), and state change broadcasts to a subscriber that is not connected. The services broadcast towallet_ui,metricsandunfinished_block_info(see the_state_changedmethods of the rpc apis); those three are named once inbroadcast_destinations. Answering them would generate a useless error back to the producing service on every event on headless nodes.The GUI copes with the new answer as it is: its service start-up wait (
Daemon.waitForServicein@chia-network/api) pings the service through the daemon and retries after one second on any failure, so start-up behaves as before, and an ordinary request now fails immediately with a message instead of hanging.Testing Notes:
New
test_daemon_replies_when_destination_is_not_connectedinchia/_tests/core/daemon/test_daemon.py, against a real daemon: a request forchia_full_nodewith no full node registered gets the error reply with the matchingrequest_id; so does a request for a service that registered on a second websocket and then disconnected (after the daemon has processed the close); broadcasts towallet_ui,metricsandunfinished_block_infoand a staleackreply to a vanished client get no reply, verified by the next received message being the answer to a followingrunning_servicescall. Without the change the test hangs on the first receive, which is the old behaviour.chia/_tests/core/daemon/test_daemon.py,test_daemon_register.py,chia/_tests/core/test_daemon_rpc.pyandchia/_tests/rpc/test_rpc_server.pypass locally (147 tests in the three daemon modules, pluschia/_tests/rpc/test_rpc_server.py); ruff and mypy are clean.🤖 Generated with Claude Code
Note
Medium Risk
Changes daemon message routing for all clients (including the GUI) when services are offline or disconnected; behavior is intentional fail-fast but alters long-standing silent-drop semantics for requests.
Overview
When a websocket message targets a Chia service that has no live daemon connection (never registered or left as an empty set after disconnect), the daemon now responds to the sender with
success: falseand"<service> is not connected to the daemon"instead of dropping the message and letting clients hang until their own timeout.Unchanged silent drops:
ackreplies to clients that are gone, and broadcasts towallet_ui,metrics, andunfinished_block_info(listed in newbroadcast_destinations) still return no reply, since nothing waits on those.Routing now treats an empty connection set like missing connections via
connections.get(destination)and a truthy check on the socket set.Adds integration test
test_daemon_replies_when_destination_is_not_connectedcovering error replies, post-disconnect behavior, and that broadcasts/stale acks stay silent.Reviewed by Cursor Bugbot for commit efe2e71. Bugbot is set up for automated code reviews on this repo. Configure here.