MoonshotAI / MoonshotAI/kimi-cli
ACP/print 流式响应静默挂死:无空闲超时、被顶替轮 partial 不落 wire(0.31.1 只覆盖 Esc 场景) || ACP/print streaming response hangs silently: no idle timeout, replaced wheel partial does not fall off the wire (0.31.1 only covers the Esc scenario)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 11.4k
- Forks
- 1.3k
- Avg merge
- 9h 47m
- Merged PRs (30d)
- 2
Description
现象
kimi CLI 0.34.0(ACP 模式,kimi acp)与 api.kimi.com 的流式对话中,偶发内容全部流式发完后连接挂死:
- 内容 delta 已全部到达(客户端能看到完整答复);
- 但终端帧(
[DONE]/finish 帧)始终不来,无错误、无超时; - CLI 无流式空闲超时配置项(官方 config.toml 文档确认),
session/prompt无限等待; - 此时用户发送下一条消息,挂死轮被静默顶替;已流式的答复从未写入 wire.jsonl
(无content.part、无usage.record、无step.end); - 结果:下一轮的模型上下文里完全没有自己上一轮发过的答复——表现为"失忆",
会把旧结论重发一遍、把已给的选项当作没列过。
证据(生产实机,2026-08-09)
kimi-code.log:最终总结的 LLM 请求发出后 12 分 21 秒零日志,
直到用户下一条消息到达才恢复(挂死轮被顶替);- 同会话两次命中:turn 1 挂死 12m21s,turn 0 末尾同形态(93 秒被顶替);
- wire.jsonl 尾部:挂死轮只有
llm.request,无任何 response/part/usage/step.end 记录。
已知边界
- 0.31.1「Esc 主动中断保留 partial 输出」修复覆盖不到本形态:静默挂死时
轮永不 finalize,用户没有按 Esc,partial 保留逻辑没有触发机会; - 社区已知同类:Kimi K2
max_tokens+stream时不发[DONE](规避=收到
finish_reason 主动 break);claude-code #61001「silently-dead stream 无限挂死」同型。
请求
- 流式空闲超时:config.toml 增加可配置的空闲看门狗(业界收敛在 4–5 分钟:
Codex CLI 300s 硬编码、Claude Code 5min、Qwen Code 240s),超时按 interrupted
收尾而不是无限等待; - 被顶替/中止轮的 partial 落盘:复用 0.31.1 Esc 路径的 partial 保留逻辑,
把已流式内容 +[Response interrupted]标记写入 wire.jsonl 与模型历史
(Cline/aider/OpenCode 均为同款打法),根治"失忆"; - ACP 侧:
session/cancel时先 flush 积压 update 再以cancelled收尾
(协议规范要求),目前静默挂死场景下 cancel 是否 flush 未验证。
客户端临时规避(已实现)
ACP client 侧看门狗:记录最后 session/update 时间,空闲超阈值主动 session/cancel,
宽限 20s 无响应杀进程;同时缓存本轮已流式文本,轮非正常结束时在下一条用户消息前
注入合成前缀「上一轮答复因传输中断未完整保存,已输出内容为:…」。
Phenomenon
In the streaming conversation between kimi CLI 0.34.0 (ACP mode, kimi acp) and api.kimi.com, occasionally** the connection hangs after all the content is streamed**:
- The content delta has all arrived (the client can see the complete reply);
- But the terminal frame (
[DONE]/finish frame) never comes, no error, no timeout; - CLI has no streaming idle timeout configuration item (confirmed by the official config.toml document),
session/promptwaits infinitely; - At this time, the user sends the next message, and the dead wheel is replaced by silence; the streamed reply is never written to wire.jsonl**
(Nonecontent.part, Noneusage.record, Nonestep.end); - Result: The next round of model context does not contain the reply that I sent in the previous round at all - manifested as "amnesia",
The old conclusions will be resent and the given options will be treated as if they have not been listed.
Evidence (actual production machine, 2026-08-09)
kimi-code.log: 12 minutes 21 seconds zero log after final summary of LLM request,
It will not resume until the user's next message arrives (the hanging wheel will be replaced);- Two hits in the same session: turn 1 hangs for 12m21s, turn 0 ends in the same form (replaced in 93 seconds);
- Tail of wire.jsonl: There is only
llm.requestin the dead wheel, and there is no response/part/usage/step.end record.
Known boundaries
- 0.31.1 "Esc actively interrupts and retains partial output" fixes this form cannot be covered: when hanging silently
The wheel never finalizes, the user does not press Esc, and the partial retention logic has no chance to trigger; - Similarity known to the community: Kimi K2 does not send
[DONE]whenmax_tokens+stream(avoidance = received
finish_reason active break); same type as claude-code #61001 "silently-dead stream infinite hang".
Request
- Streaming idle timeout: config.toml adds configurable idle watchdog (industry convergence at 4–5 minutes:
Codex CLI 300s hard coding, Claude Code 5min, Qwen Code 240s), press interrupted when timeout
Finish instead of waiting indefinitely; - Partial placement of the replaced/aborted round: Reuse the partial retention logic of the 0.31.1 Esc path,
Write streamed content +[Response interrupted]tag to wire.jsonl and model history
(Cline/aider/OpenCode are all the same style of play), which can cure "amnesia"; - ACP side: When
session/cancelis executed, first flush the backlog and update and then end withcancelled
(Required by the protocol specification). Currently, whether cancel or flush is not verified in the silent hang scenario.
Client temporary circumvention (implemented)
ACP client side watchdog: records the last session/update time, active session/cancel when idle exceeds the threshold,
Grace 20s to kill the process without response; cache the streamed text of this round at the same time, and before the next user message when the round ends abnormally
Inject the synthetic prefix "The previous round of reply was not completely saved due to transmission interruption, and the output content is:...".
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 the config.toml documentation and the kimi acp session/prompt and session/cancel entry points, then trace the 0.31.1 Esc partial-retention path and wire.jsonl recording. Done means an idle stream becomes interrupted after a configurable timeout, cancellation flushes pending updates, and streamed partial output is recorded in wire.jsonl and model history.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100