Summary
On macOS, the kimi web server accepts WebSocket connections and subscriptions but never pushes any session events, while its GET /sessions/{id}/transcript REST API serves a stale in-memory snapshot that never observes turns written by other processes (e.g. the terminal CLI). User-visible symptoms: web UI permanently stuck at "connecting…", sent messages appear to be swallowed, and the only workaround is restarting the server.
All evidence below was collected with protocol-level tooling (CDP-injected WebSocket sniffer, raw WebSocket reproduction, direct wire.jsonl comparison).
Environment
- kimi-code CLI/Server 0.42.0 (same class of symptoms since 0.41.0)
- macOS 26.6.2, MacBook Pro
- Server managed by launchd, listening on 127.0.0.1:58627
- Control group: same version on a fresh Ubuntu (Hyper-V/WSL) machine works fine → likely tied to local server-side state
Symptoms
- Open any session (including brand-new empty ones) in the bundled web UI
- Footer permanently shows "connecting… opening session…"; sending a message produces no echo, toast "WebSocket error"
- After F5, history up to the latest fold point renders (REST + transcript fold work), but realtime events never arrive
Protocol-level evidence
Page's own WS client frames (CDP sniffer):
NEW: ws://127.0.0.1:58627/api/v1/ws?client_id=web_…
OPEN
MSG: server_hello (protocol_version 2, heartbeat_ms 10000)
SEND: client_hello → ack code:0 success
SEND: subscribe + subscribe_v2 → both ack code:0 success, session accepted
MSG: transcript.reset (seq 803, epoch ep_…) ← the ONLY session-related push ever seen
Then: ping/pong heartbeats only — zero transcript deltas / session events,
even while the agent is actively executing tool calls in that session
- Reproduced identically with a raw
new WebSocket(...) (subprotocol kimi-code.bearer.<token>): handshake + subscription acks succeed, zero events afterwards
- Server log has no error-level entries; REST requests all 200
Root cause confirmed: stale in-memory transcript snapshot
While building a minimal read-only web client against the official backend, we confirmed:
- Disk
wire.jsonl had reached turn 232, while GET /sessions/{id}/transcript returned at most turn 220 — turns written by the terminal CLI process are invisible to the web server process.
- Session list metadata (
last_prompt, updated_at) is fresh — the list knows there are updates, the transcript endpoint can't serve them.
- Restarting the web server forces a re-read from disk and temporarily fixes it — matching the long-standing user symptom "must restart kimi web to see new messages".
Two additional wire-protocol defects found
- turnId is reused after full compaction: the first new turn after a full compaction reused an old turnId from days ago (both old and new turn had id 217; later turn 234 did not). Any client merging by turnId will append new content into a stale mid-list turn object — new messages "disappear", the old turn's prompt gets overwritten.
- Frames may arrive after
turn.ended: trailing content.part frames with the same turnId were observed after turn.ended. Clients that destroy the turn object on ended will spawn ghost turns.
Transcript fold pipeline freezes periodically
On the same server, fold advances for only a few minutes after each restart:
| server start |
frozen at |
fold stuck at |
| 09:42 |
~09:44 |
t102 |
| 10:31 |
~10:50 |
t108 |
| 10:47 (after full state reset) |
~10:50 |
t108 (30 min no progress; wire.jsonl gained 16 turns in the same window) |
Also observed: session index reconciliation failed: AggregateError: batch failed on 1/9 shard(s) repeating ~325 times over multiple days (later became ENOENT on a missing shard dir after we moved the query-store away). A 40MB corrupt query-store backup is preserved and can be provided privately if useful.
Exclusions
- Browser cache/extensions/profile — reproduced in a clean browser profile
- Frontend/server version mismatch — the bundled frontend version is as shipped
- Session data corruption — wire.jsonl keeps appending normally; fold catches up after restart
- Full server state reset (query-store, search-index, session_index.jsonl all rebuilt) — fold recovers, WS push still silent
Workaround that proves the fix direction (reference implementation)
We built a ~500-line temporary read-only web client that has run 24h+ without errors:
- Transcript read directly from wire.jsonl (append-only, visible to all processes), incremental parsing with cached file offset — messages never "disappear", no restarts needed
- Send/abort/file upload still go through the official REST API — proving only the read path is broken, the write path is healthy
- Client-side compatibility rules for the wire defects: pair
tool.result by toolCallId (it has no turnId); clear turnId mappings on context.apply_compaction; keep turn objects after turn.ended; determine "running" from the last turn-kind entry + wire mtime
Note: this report was investigated and assembled by Kimi Code itself (with user authorization) — which shows how high the debugging bar is for regular users. A self-service "reset server event pipeline state" switch would already help a lot.
Expected
- Fix the server-side event push pipeline (or document where its state lives so it can be cleared/rebuilt manually)
- transcript endpoint should treat wire.jsonl as the source of truth (invalidate/re-read the in-memory snapshot)
- Never reuse turnId after compaction (or provide a globally monotonic turn sequence)
- Document the turn lifecycle: trailing frames after
ended are legal
Summary
On macOS, the
kimi webserver accepts WebSocket connections and subscriptions but never pushes any session events, while itsGET /sessions/{id}/transcriptREST API serves a stale in-memory snapshot that never observes turns written by other processes (e.g. the terminal CLI). User-visible symptoms: web UI permanently stuck at "connecting…", sent messages appear to be swallowed, and the only workaround is restarting the server.All evidence below was collected with protocol-level tooling (CDP-injected WebSocket sniffer, raw WebSocket reproduction, direct wire.jsonl comparison).
Environment
Symptoms
Protocol-level evidence
Page's own WS client frames (CDP sniffer):
new WebSocket(...)(subprotocolkimi-code.bearer.<token>): handshake + subscription acks succeed, zero events afterwardsRoot cause confirmed: stale in-memory transcript snapshot
While building a minimal read-only web client against the official backend, we confirmed:
wire.jsonlhad reached turn 232, whileGET /sessions/{id}/transcriptreturned at most turn 220 — turns written by the terminal CLI process are invisible to the web server process.last_prompt,updated_at) is fresh — the list knows there are updates, the transcript endpoint can't serve them.Two additional wire-protocol defects found
turn.ended: trailingcontent.partframes with the same turnId were observed afterturn.ended. Clients that destroy the turn object onendedwill spawn ghost turns.Transcript fold pipeline freezes periodically
On the same server, fold advances for only a few minutes after each restart:
Also observed:
session index reconciliation failed: AggregateError: batch failed on 1/9 shard(s)repeating ~325 times over multiple days (later became ENOENT on a missing shard dir after we moved the query-store away). A 40MB corrupt query-store backup is preserved and can be provided privately if useful.Exclusions
Workaround that proves the fix direction (reference implementation)
We built a ~500-line temporary read-only web client that has run 24h+ without errors:
tool.resultby toolCallId (it has no turnId); clear turnId mappings oncontext.apply_compaction; keep turn objects afterturn.ended; determine "running" from the last turn-kind entry + wire mtimeNote: this report was investigated and assembled by Kimi Code itself (with user authorization) — which shows how high the debugging bar is for regular users. A self-service "reset server event pipeline state" switch would already help a lot.
Expected
endedare legal