Skip to content

Answer requests for services that are not connected to the daemon instead of dropping them - #21393

Open
jlobue10 wants to merge 2 commits into
Chia-Network:mainfrom
jlobue10:fix/daemon-reply-unregistered-destination
Open

jlobue10 wants to merge 2 commits into
Chia-Network:mainfrom
jlobue10:fix/daemon-reply-unregistered-destination

Conversation

@jlobue10

@jlobue10 jlobue10 commented Sep 11, 2026

Copy link
Copy Markdown

Purpose:

Make a request for a service that is not connected to the daemon fail visibly instead of silently. WebSocketServer.handle_message returned None when 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_wallet was 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 (same request_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: true messages), and state change broadcasts to a subscriber that is not connected. The services broadcast to wallet_ui, metrics and unfinished_block_info (see the _state_changed methods of the rpc apis); those three are named once in broadcast_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.waitForService in @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_connected in chia/_tests/core/daemon/test_daemon.py, against a real daemon: a request for chia_full_node with no full node registered gets the error reply with the matching request_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 to wallet_ui, metrics and unfinished_block_info and a stale ack reply to a vanished client get no reply, verified by the next received message being the answer to a following running_services call. 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.py and chia/_tests/rpc/test_rpc_server.py pass locally (147 tests in the three daemon modules, plus chia/_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: false and "<service> is not connected to the daemon" instead of dropping the message and letting clients hang until their own timeout.

Unchanged silent drops: ack replies to clients that are gone, and broadcasts to wallet_ui, metrics, and unfinished_block_info (listed in new broadcast_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_connected covering 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.

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread chia/daemon/server.py
Comment thread chia/daemon/server.py
…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
@jlobue10

Copy link
Copy Markdown
Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 emlowe added the Changed Required label for PR that categorizes merge commit message as "Changed" for changelog label Sep 15, 2026
@jlobue10
jlobue10 deployed to windows-code-signing September 15, 2026 16:38 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changed Required label for PR that categorizes merge commit message as "Changed" for changelog community-pr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants