Skip to content

fix(kap-server): add WebSocket heartbeat to survive proxy idle timeouts - #2813

Merged
wbxl2000 merged 1 commit into
mainfrom
fix/kap-server-ws-heartbeat
Aug 11, 2026
Merged

fix(kap-server): add WebSocket heartbeat to survive proxy idle timeouts#2813
wbxl2000 merged 1 commit into
mainfrom
fix/kap-server-ws-heartbeat

Conversation

@wbxl2000

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below (reported by an internal user running kimi web behind 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. When kimi web runs behind a reverse proxy / gateway with an idle connection timeout (30s defaults are common, e.g. nginx proxy_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

  • The server now sends an application-level ping frame every 10s and advertises heartbeat_ms in server_hello. The protocol schema already defined both, and every shipped WS client (the web UI, kimi-inspect) already answers ping with pong — the field was simply never sent, so this works with already-released clients.
  • Application-level rather than protocol-level WS ping: browser JS cannot observe protocol ping/pong frames, and the client's stale-socket detector keys on incoming message frames.
  • Liveness/reaping: any inbound frame (a 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 1001 heartbeat timeout, so dead connections get reaped instead of leaking.
  • The interval is injectable (heartbeatIntervalMs) for tests.

Verification

  • New unit tests (6): hello advertises the interval, 10s default, pings carry unique nonces while the peer answers, reap after two silent cycles (no further pings), any inbound frame resets the liveness window, heartbeat stops on close. Full kap-server suite: 66 files / 1023 tests pass; typecheck clean; no new lint warnings.
  • Live A/B behind a local WS proxy with a 30s idle timeout:
    • Before: connection killed at exactly 30.0s — reproduces the reported issue.
    • After: ping every 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

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

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-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6ab47dc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

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

@pkg-pr-new

pkg-pr-new Bot commented Aug 11, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@6ab47dc
npx https://pkg.pr.new/@moonshot-ai/kimi-code@6ab47dc

commit: 6ab47dc

@wbxl2000
wbxl2000 merged commit 619564d into main Aug 11, 2026
15 checks passed
@wbxl2000
wbxl2000 deleted the fix/kap-server-ws-heartbeat branch August 11, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant