fix(kap-server): add WebSocket heartbeat to survive proxy idle timeouts - #2813
Merged
Conversation
The v1 WS connection had no keepalive: by design it stayed open until the client disconnected, which only holds for direct connections. Behind a reverse proxy or gateway with an idle timeout (30s defaults are common), any quiet stretch — e.g. waiting on a slow model response — got the connection killed, surfacing as a recurring 'Realtime connection error' in the web UI. Send an application-level ping every 10s and advertise heartbeat_ms in server_hello (the schema and all shipped clients already answer pong). Application-level rather than protocol-level ping because browser JS cannot observe the latter, and the client's stale-socket detector keys on incoming message frames. Any inbound frame refreshes liveness; after two silent cycles the connection is presumed half-open and closed with 1001 so dead peers get reaped instead of leaking.
🦋 Changeset detectedLatest commit: 6ab47dc The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
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.
Related Issue
No linked issue — the problem is explained below (reported by an internal user running
kimi webbehind a gateway).Problem
The v1 WebSocket connection (
/api/v1/ws) has no keepalive: by design it stayed open until the client disconnected, which only holds for direct connections. Whenkimi webruns behind a reverse proxy / gateway with an idle connection timeout (30s defaults are common, e.g. nginxproxy_read_timeout), any quiet stretch — notably waiting on a slow model response, where ttft of 30–60s+ is routine — gets the connection killed. Users see a "Realtime connection error" toast every ~30s and the UI stalls until reconnect/resync.What changed
pingframe every 10s and advertisesheartbeat_msinserver_hello. The protocol schema already defined both, and every shipped WS client (the web UI, kimi-inspect) already answerspingwithpong— the field was simply never sent, so this works with already-released clients.pong, but also ordinary control traffic) refreshes the connection's liveness timestamp. After two full silent cycles (~20s) the peer is presumed half-open (laptop asleep, network silently gone) and the connection is closed with 1001heartbeat timeout, so dead connections get reaped instead of leaking.heartbeatIntervalMs) for tests.Verification
pingevery 10s keeps the proxy's idle timer reset; connection survives (75s run). A client that never answers is reaped by the server at 20s with 1001.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.