feat(chat): auto-reconnect the daemon on hub drop, without losing handle state #18
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/auto-reconnect"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
The hub (cbcl-bus) is a single instance with an immediate deploy strategy — it must be, because its persistence is single-writer (Litestream → one bucket + Mnesia → one node). So every redeploy replaces the machine and drops all sockets at once (
hub_closed/ "peer closed without TLS close_notify" across every handle simultaneously).Today the chat receive loop just marks the handle unhealthy and ends. The operator then bounces the daemon — which wipes handle state (
agents: 0) — and since pairing codes are one-time, the codes on hand are now spent (UNKNOWN_PAIRING), forcing a full re-pair of every agent. A routine hub restart shouldn't cost the whole agent fleet.What
Ride through hub restarts automatically, preserving handle + MLS state.
connect → bootstrap → signed hello → join-ack → MLS on_roomcfg/(create)/join_frames → announcesequence intoconnect_and_join(), preserving the exact frame order and every validation (downgrade refusal, join-ack gating,UndeclaredDialect, CBCL announce self-check). Called for the first connect and re-run verbatim on each reconnect.run_receive_loopreturnsLoopExit { Stopped, LocalClosed, HubClosed }.spawn_receive_loopwraps it in an outer loop that reconnects only forHubClosed, reusing the same identity,MlsSession, responder, store handle, and both control channels.daemon stopduring backoff aborts cleanly.MlsSessionreused;mls_createis false on every reconnect so the group is created at most once — re-creating would mint a newgroup_idand fork every peer (WrongGroupId).join_framesre-publishes KeyPackages + idkey to the restarted hub, and the owner re-adds present members on presence.AgentStore::mark_reconnectingreflects the outage without firing the connection's close signal. This is load-bearing:mark_unhealthyconsumes/firesclose_tx, which would resolve the loop'sclose_rxand be indistinguishable from an explicit stop — aborting the very reconnect we want. TheHubClosedarms therefore no longer callmark_unhealthy;mark_connectedrestores health in place on rejoin. A unit test pins the invariant.Testing
cargo build— cleancargo clippy --all-targets -- -D warnings— cleancargo test— 310 passed, 3 ignored (+1 new test:reconnecting_preserves_close_signal_then_connected_restores)Companion change
The cbcl-bus web client gained matching auto-reconnect (exponential backoff, resume channels + MLS from persisted state, immediate retry on
online/tab-focus), so a redeploy is a brief blip rather than a dead tab. This PR gives the daemon the equivalent.🤖 Generated with Claude Code
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.