MoonshotAI / MoonshotAI/kimi-code

[VSCode] Streamed output duplicated/interleaved when two views race to open the same session

Open
#2,799 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

Bug description

Sometimes all assistant output in a chat appears twice, strictly interleaved chunk-by-chunk:

TheThe roaring roaring run run died died with with the the session session close close;; I'll I'll restart restart it it …

Tool labels and "Thinking" headings are doubled as well. The strict alternation (delta1, delta1, delta2, delta2, …) indicates two consumers appending the same delta stream concurrently.

Root cause

KimiRuntime.openSession / attachResumedSession check this.sessions.get(id) and only then await several RPCs (resumeSession, metadata updates, detachView, …) before wrapSession registers the runtime in the map. Two concurrent calls for the same session id (sidebar + editor panel opening at once, or a window reload overlapping a reattach) both see an empty map, both resume, and both call wrapSession, which unconditionally sessions.set(session.id, runtime) — the later runtime overwrites the earlier one in the map, but the earlier SessionRuntime is never closed: its session.onEvent listener (and approval/question handler registrations, which are keyed by session id in the SDK rpc layer) stay live.

Both runtimes then adapt and broadcast every SDK event. When the same webviewId is subscribed in both (both racing calls subscribe it, or it re-attaches later), the webview's appendOrCreate concatenates both copies into the same item — producing the interleaved duplication. The webview store has no id-based dedup, so nothing downstream catches it.

Reproduction

Timing-dependent in production, but deterministic in a unit test: two concurrent openSession calls with the same sessionId create two SessionRuntimes (observable via the session's onEvent subscription count = 2), and one emitted assistant.delta produces two ContentPart broadcasts per view.

Suggested fix

wrapSession should return the existing runtime when this.sessions already has one for the session id. A resumed Session handle is inert until wrapped (its constructor registers nothing — listeners and approval handlers are only installed by the SessionRuntime constructor), so the loser's handle can simply be dropped; the shared engine session must NOT be closed (that would kill it for the winning runtime too).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at KimiRuntime.openSession, attachResumedSession, and wrapSession, then inspect the existing unit-test setup for concurrent session opening. Reproduce two calls with the same sessionId and verify that only one SessionRuntime subscribes to onEvent and one ContentPart broadcast reaches each view.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.