Repository navigation
fix: stop the coordinator piling up beacon sockets on port 19877 - #477
Merged
Merged
Conversation
Concurrent startAdvertising and discover calls each passed the isListening check before the first bind finished, so each bound its own socket and the last one overwrote this.socket, leaving the others open on the beacon port. An error event flipped isListening off without closing the socket, so the next call bound a second socket beside it, and a failed bind never called back, leaving its caller waiting forever. Every caller now awaits one shared bind promise, an error on a bound socket no longer replaces it, a failed bind closes its socket and rejects, and stop() waits for a bind in flight so the socket it produces is the one closed. The port and socket factory are injectable so tests count real sockets. Refs #476
The front polls on a fixed interval whether or not the previous tick finished, and an attach can wait out a room join's approval window, many intervals long. Every tick in that window attached the same session again, each attach building another mesh store with its own sockets, and only the last record to finish was kept, so the rest ran for the life of the process. An attach finishing after stop() was likewise kept fronted with nothing to detach it. Sessions with an attach in flight are now skipped, and a record whose attach finishes after stop() is detached at once. Refs #476
A fronted session's store was built before registration and its project room, so a failure in either left the store running while the front retried on the next tick with a new one. Detach likewise skipped shutdown when marking the agent offline failed. Each failure left a running store, with its own sockets and timers, behind. A failed attach now shuts its store down before rethrowing, and detach shuts the store down whether or not the offline patch went out. Refs #476
…sence Every store the cc-peer front built for a fronted session went through createBridgeMeshFromIdentity, which defaults firstContactPort to the shared beacon port, so the coordinator process bound one more UDP socket on that port for every Claude Code session it fronted. With reuseAddr every one of those sockets received every probe and beacon on the machine and answered each probe with a multicast and a broadcast beacon of its own, multiplying the traffic by the number of fronted sessions. The fronted stores live in the coordinator's process and reach the mesh through its coordinator port, so they need no presence: createBridgeMesh now takes firstContactPort false to build a store without one, and the front passes it. Fixes #476
MeshStore.shutdown() stopped the first-contact presence but never its DiscoveryManager, so a store that had run mesh_advertise kept its mDNS beacon socket and timer on the beacon port for the life of the process after it shut down. DiscoveryManager gains stop(), which stops every backend and forgets every advertisement, and shutdown() calls it. The default backend set moves into createDefaultDiscovery so the store names no concrete backend. Refs #476
Mearman
marked this pull request as ready for review
November 3, 2026 15:31
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins. |
Contributor
|
🎉 This PR is included in version 8.15.23 🎉 The release is available on: Your semantic-release bot 📦🚀 |
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.
Fixes #476
The process holding many sockets on
*:19877is the machine's coordinator bridge, which runs the default cc-peer front. Every store the front builds for a fronted Claude Code session went throughcreateBridgeMeshFromIdentity, whosefirstContactPortdefaults to the shared beacon port, so the coordinator bound one morereuseAddrsocket on that port per fronted session (a live coordinator here held 14 on 19877 next to 15 loopback data listeners, one per fronted store, while every other bridge process held 1). WithreuseAddreach of those sockets receives every probe and beacon on the machine and answers every probe with a multicast plus a broadcast beacon, so traffic and CPU grow with the number of fronted sessions. The front then leaked stores on top of that: ticks run on a fixed interval while an attach can wait out a room join's approval window, so the same session was attached again each tick and only the last record kept, and a failed attach or detach left its store running.The changes:
firstContactPort: false(new option on the bridge-mesh factories): they live in the coordinator's process and reach the mesh through its coordinator port, so they need no presence.CcPeerFrontskips a session whose attach is still in flight, and detaches a record whose attach finishes afterstop().MdnsDiscoveryBackendshares one bind promise across concurrent callers, keeps (never replaces) a bound socket that reports an error, closes and rejects on a failed bind instead of hanging, andstop()waits for a bind in flight.MeshStore.shutdown()now stops the discovery backends, so an advertisement's beacon socket no longer outlives its store.Each fix has a test that failed before it:
src/test/discovery-mdns.unit.test.ts, the "an attach outlasting the poll interval" block incc-peer-front-controller.unit.test.ts, theattachFrontedSessionand detach-failure cases incc-peer-front-relay.unit.test.ts,cc-peer-front-runtime-mesh.unit.test.ts, the twofirstContactPortsocket-count cases inbridge-mesh.integration.test.ts, and the discovery-backend case inmesh-store-orchestration.unit.test.ts.