Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Only write entries that are worth mentioning to users.

## Unreleased

- Shell: Fix cancelled shell commands not properly terminating child processes — when a running command is cancelled, the subprocess is now explicitly killed to prevent orphaned processes

## 1.21.0 (2026-03-12)

- Shell: Add inline running prompt with steer input — agent output is now rendered inside the prompt area while the model is running, and users can type and send follow-up messages (steers) without waiting for the turn to finish; approval requests and question panels are handled inline with keyboard navigation
Expand Down
2 changes: 2 additions & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ This page documents the changes in each Kimi Code CLI release.

## Unreleased

- Shell: Fix cancelled shell commands not properly terminating child processes — when a running command is cancelled, the subprocess is now explicitly killed to prevent orphaned processes

## 1.21.0 (2026-03-12)

- Shell: Add inline running prompt with steer input — agent output is now rendered inside the prompt area while the model is running, and users can type and send follow-up messages (steers) without waiting for the turn to finish; approval requests and question panels are handled inline with keyboard navigation
Expand Down
2 changes: 2 additions & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## 未发布

- Shell:修复取消的 Shell 命令未正确终止子进程的问题——当运行中的命令被取消时,子进程现在会被显式杀死,防止产生孤儿进程

## 1.21.0 (2026-03-12)

- Shell:新增内联运行提示与 steer 输入——模型运行时 Agent 输出直接渲染在提示区域内,用户无需等待轮次结束即可输入并发送后续消息(steer);审批请求和问答面板支持内联键盘交互
Expand Down
3 changes: 3 additions & 0 deletions src/kimi_cli/tools/shell/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ async def _read_stream(stream: AsyncReadable, cb: Callable[[bytes], None]):
timeout,
)
return await process.wait()
except asyncio.CancelledError:
await process.kill()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard kill call when cancelled command already exited

If cancellation is delivered just after the subprocess has already finished (for example, user presses Esc as a short command exits), this unconditional kill() can raise ProcessLookupError (as asyncio.subprocess.Process.kill() does on exited processes). That replaces the expected CancelledError with an unexpected failure, so the shell turn may surface an internal error instead of a clean interruption; check process.returncode or suppress ProcessLookupError during cleanup.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard kill when cancellation races process exit

When cancellation lands right after the subprocess has already exited, await process.kill() can raise ProcessLookupError (the local KAOS wrapper directly calls asyncio.subprocess.Process.kill()), which replaces the expected CancelledError with an internal failure. This can surface sporadic tool errors when users interrupt near command completion; check process.returncode or suppress ProcessLookupError before re-raising cancellation.

Useful? React with 👍 / 👎.

raise
except TimeoutError:
await process.kill()
raise
Comment on lines +122 to 126

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On cancellation/timeout you call process.kill() and immediately re-raise, but you never wait() afterwards. With asyncio subprocesses (and KAOS’ own tests), killing without waiting can leave a zombie process until GC. Consider ensuring the process is reaped (e.g., kill then wait with a short timeout) in both the CancelledError and TimeoutError paths.

Suggested change
await process.kill()
raise
except TimeoutError:
await process.kill()
raise
try:
await process.kill()
try:
# Ensure the process is reaped to avoid zombies.
await asyncio.wait_for(process.wait(), 1.0)
except Exception:
# Ignore errors during best-effort cleanup.
pass
finally:
raise
except TimeoutError:
try:
await process.kill()
try:
# Ensure the process is reaped to avoid zombies.
await asyncio.wait_for(process.wait(), 1.0)
except Exception:
# Ignore errors during best-effort cleanup.
pass
finally:
raise

Copilot uses AI. Check for mistakes.
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading
Loading