MoonshotAI / MoonshotAI/kimi-code

TUI /web exits the process (exit 0) instead of handing off to foreground kimi web

Open
#3,338 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

你运行的 Kimi Code 版本是?

0.39.0

你使用的是哪个开放平台/订阅?

Kimi Code(kimi login

你使用的是哪个模型?

无关(/web 交接阶段就会退出)

你的电脑平台是?

Darwin 25.5.0 arm64 arm

你遇到了什么问题?

在 TUI 里对已有会话执行 /web 后,整个 kimi 进程以 exit 0 退出,没有停在前台 web server,也经常看不到 Kimi server ready 横幅。终端直接回到 shell。

对比:同一台机器、同一版本,在 shell 里跑 kimi web --no-open 可以一直前台挂着,127.0.0.1:58627 可访问。

复现步骤?
  1. kimi(或 kimi -S <existing-session>)进入 TUI,确认已有 session。
  2. 输入 /web 回车。
  3. TUI 拆掉后进程结束,exit code 0。没有持续监听的 web server。
期望的行为是什么?

文档/代码注释里的设计:TUI 退出后 同一进程变成前台 kimi web,打印 ready banner + session 深链,直到 Ctrl+C。

apps/kimi-code/src/tui/commands/web.ts

/**
 * `/web` — hand the current session off to the browser.
 *
 * Always starts a new server: the TUI shuts down and this process becomes the
 * server, running in the foreground attached to this terminal ...
 */
export async function handleWebCommand(host: SlashCommandHost): Promise<void> {
  startNewServerAfterExit(host, session.id);
  await host.stop();
}

run-shell.tsonExit 也会 await tui.exitForegroundTask(exitCode) 而不是立刻 process.exit

补充信息

原因(与 #3336 不同)

/web 本身不走 Remote Control,也不读 OAuth 槽。失败发生在 TUI teardown 之后、HTTP listen 之前。

  1. TUI stop()terminal.stop()process.stdin.pause()packages/pi-tui/src/terminal.ts,注释说是为了避免 raw mode 关掉后 Ctrl+D 打到父 shell):
process.stdin.pause();
if (process.stdin.setRawMode) {
  process.stdin.setRawMode(this.wasRaw);
}

暂停后的 stdin 不再占用 event loop。

  1. onExitshutdownTelemetry、关掉 harness/MCP 之后,再 await startServerForeground()runServerInProcess 在 listen 之前有 await startServer(...)

  2. runServerInProcess 末尾的 return new Promise<never>(() => {}) 并不能 维持 event loop(注释写错了)。进程活着靠的是 listen socket 以及尚未 pause 的 stdin。

  3. CLI kimi web 没有先拆 TUI,stdin 仍是 ref'd TTY,所以 await startServer 期间进程不会退出。/web 已经 pause 了 stdin、拆掉 MCP/timer,listen 还没绑上 → Node 走 beforeExitexit 0process.exit() 不会被调用,所以 catch 里的 Failed to start server 也打不出来。

实测:TUI /web 后进程约 1–2 秒结束;同一时刻日志会出现一次新的 query-store opening(server 开始启动),但 http.Server.listen 来不及发生。

Workaround

不要用 /web 做交接。另开终端:

kimi web

用打印的 URL 打开当前 session(/sessions/<id>#token=...)。

建议修复

在调用 exitForegroundTask / startServerForeground 之前先挡住 event loop,例如:

  • process.stdin.resume()(交接完成后 web server 的 listen 会接上),或
  • ref'd setInterval/setTimeout 包住 await startServer(...),listen 成功后再清掉,或
  • 先 listen,再拆 TUI。

同时修正 runServerInProcess 里 “hanging Promise keeps the event loop alive” 的注释——它做不到。

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 with apps/kimi-code/src/tui/commands/web.ts and run-shell.ts to trace the handoff after host.stop(), then inspect packages/pi-tui/src/terminal.ts and the in-process server startup path. Reproduce TUI /web and compare it with kimi web --no-open. Done means the TUI hands off to a foreground web server, prints the ready banner and session URL, and remains alive until Ctrl+C.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.