MoonshotAI / MoonshotAI/kimi-cli
Bug: Web UI "Connecting to session..." infinite spinner when switching sessions
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 11.4k
- Forks
- 1.3k
- Avg merge
- 9h 47m
- Merged PRs (30d)
- 2
Description
Version: kimi-cli 1.48.0 (Homebrew, macOS 26.4, arm64)
Component: kimi web — Web UI (Technical Preview)
Browser: Chrome 150.0.7871.187
Description
When switching between sessions in the Web UI (http://127.0.0.1:5494), the UI shows an infinite "Connecting to session..." spinner. The session never connects. This happens consistently on every session switch — not just occasionally.
Restarting the kimi web server temporarily resolves the issue, but it returns after the first session switch.
Steps to Reproduce
- Start
kimi web - Open the web UI in Chrome
- Have 2+ existing sessions
- Click on a different session in the sidebar
- Result: "Connecting to session..." spinner appears and never resolves
- All subsequent session switches also fail
Observed Behavior
lsof -i :5494showsCLOSE_WAITTCP connections accumulating — the server doesn't properly close WebSocket connections after the client disconnects- The healthcheck endpoint (
/healthz) responds{"status":"ok"}— the server process itself is alive - Only 1 active
ESTABLISHEDconnection exists while others are stuck inCLOSE_WAIT
Root Cause Analysis
Traced through the source code (sessions.py, process.py, frontend JS):
1. end_replay() livelock under message throughput
end_replay() in process.py loops acquiring _ws_lock, copying buffered messages, and sending them. If _read_loop is actively broadcasting, the buffer refills between iterations, causing the loop to spin indefinitely. This blocks send_status_snapshot() from ever executing.
2. Frontend 15-second timeout → infinite reconnect loop
The frontend waits up to 15 seconds for a session_status message after history_complete. If send_status_snapshot() is blocked (by issue #1), the timeout fires, triggering reconnect(), which starts the cycle over: disconnect → 50ms delay → connect → replay → timeout → reconnect...
3. _broadcast() blocks on dead WebSockets
_broadcast() iterates all websockets in _websockets set and calls await ws.send_text() sequentially. A dead/closing WebSocket blocks delivery to all other connected clients.
4. remove_websocket() doesn't call ws.close()
When a WebSocket is removed from the set, the TCP connection isn't explicitly closed. The transport only closes when the handler function returns, but the handler may be stuck in receive_text().
Expected Behavior
Session switching should work seamlessly — disconnect from current session's WebSocket, connect to new session's WebSocket, replay history, and show the session.
Workaround
Restart the web server:
kill $(lsof -ti :5494); sleep 2; kimi web --no-open &
Related
- PR #1669: "Improve WebSocket reconnection experience in Web UI" — addresses flickering but not the core CLOSE_WAIT / livelock issue
Environment
- macOS 26.4 (Apple Silicon)
- kimi-cli 1.48.0 (Homebrew)
- Chrome 150.0.7871.187
- Multiple sessions (5-6 workers active)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with sessions.py, process.py, and the frontend JavaScript, tracing end_replay(), _broadcast(), and remove_websocket(). Reproduce switching among multiple sessions while watching the WebSocket connections, then verify that replay completes, session_status arrives, switching continues to work, and CLOSE_WAIT connections do not accumulate.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, python
- Domain
- backend, cli, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100