CopilotClient.stop() leaks the CLI server's child process tree on Windows (orphaned node/copilot.exe per session)
维护者通常 1 天内回复
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 52/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 冷清
- 技术栈
- python
调研方向
检查 client.py,尤其是 CopilotClient.stop() 和 StdioRuntimeConnection 的进程创建,然后跟踪后代进程,复现 issue 中的 Windows create_session()/stop() 循环。比较 Windows 与 POSIX 上的生命周期行为,并考虑所述的 Job Object 或 process-group 方案。当停止一个已生成的客户端后不再留下孤立的 node.exe 或 copilot.exe 后代进程时,即表示完成。
由索引模型根据 Issue 内容生成。
描述
Summary
On Windows, CopilotClient.stop() does not terminate the CLI server's child process tree — only the top-level launcher process. Every create_session() / stop() cycle therefore orphans a full copilot process tree (node.exe + the copilot.exe broker/worker/webview helpers) that survives until its own idle timeout. In a long-running app that creates one client/session per unit of work (e.g. a batch/eval harness), this is a deterministic ~1 leaked process tree per session, which accumulates and eventually OOMs the host.
Environment
github-copilot-sdk(Python)1.0.0b7(also visible by inspection in currentclient.py)- Copilot CLI runtime
1.0.66-0 - Windows 11, Python 3.11 (miniconda)
- Transport:
StdioRuntimeConnection,use_logged_in_user=True
Root cause
CopilotClient.stop() only calls terminate() / kill() on its own launcher Popen (self._process):
# client.py (stop)
if self._process and not self._is_external_server:
self._process.terminate()
try:
self._process.wait(timeout=5)
except subprocess.TimeoutExpired:
self._process.kill()
self._process = None
On Windows, terminating the launcher does not cascade to its descendants. The launcher (copilot.cmd → cmd.exe) spawns node.exe, which spawns the real copilot.exe server + helper processes; these are left orphaned and alive after stop().
Reproduction
import asyncio, psutil
from copilot import CopilotClient, StdioRuntimeConnection
CLI = r"C:\Users\<you>\AppData\Roaming\npm\copilot.cmd"
def n_copilot():
return sum(p.info["name"] == "copilot.exe"
for p in psutil.process_iter(["name"]))
async def main():
for i in range(5):
client = CopilotClient(
connection=StdioRuntimeConnection(CLI, []),
use_logged_in_user=True,
)
session = await client.create_session()
await session.send("hello")
# ...consume events until SessionIdle...
await session.disconnect()
await client.stop()
print(f"iter {i}: copilot.exe alive = {n_copilot()}")
asyncio.run(main())
Observed (Windows): copilot.exe alive grows 1, 2, 3, 4, 5 — one orphaned tree per iteration, none reaped by stop().
Expected
After await client.stop() (for a client that spawned the server), the entire CLI server process tree should be terminated, leaving no orphaned node.exe / copilot.exe processes.
Workaround
Capture the launcher PID after create_session() and kill the whole tree explicitly on teardown — e.g. psutil.Process(pid).children(recursive=True) + kill all (enumerate before killing the parent), or taskkill /F /T /PID on Windows / os.killpg on POSIX.
Suggested fix
Have the SDK own the process-tree lifecycle so stop() reaps descendants:
- Windows: assign the launcher to a Job Object with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, so the OS atomically kills the tree when the job/handle closes. - POSIX: spawn with
start_new_session=Trueandos.killpg(os.getpgid(pid), SIGKILL)on stop. - Or a
psutil-based recursive child kill insidestop().
This is likely related to the CLI-side reports of orphaned processes (e.g. github/copilot-cli#1368, #2279) but is reproducible purely through the SDK's own create_session()/stop() lifecycle.
- 主要语言
- TypeScript
- 星标
- 10.5k
- 派生
- 1.5k
- 平均合并
- 1 天 7 小时
- 30 天内合并 PR
- 64
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
github/copilot-sdk 的其他 Issue
-
Clarify SDK architecture and in-process runtime transport可能已有人在做 @KalebCole 于 4 天前认领。 未关闭documentation
难度 1/5 1 小时以内 新手友好度 92/100
github/copilot-sdk#2804 · 1 条评论 ·
维护者通常 1 天内回复
-
Python ModelLimits drops max_output_tokens from model metadata可能已有人在做 @HDMowri 于 6 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 78/100
github/copilot-sdk#2798 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100
github/copilot-sdk#2793 ·
维护者通常 1 天内回复
-
agentic-workflows
难度 2/5 1-3 小时 新手友好度 68/100
github/copilot-sdk#2782 ·
维护者通常 1 天内回复
-
Rust: subagent lifecycle hooks are logged as unknown可能已有人在做 @hackberry-lab 于 9 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 88/100
github/copilot-sdk#2781 ·
维护者通常 1 天内回复
查看 github/copilot-sdk 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 85/100
umbraco/Umbraco-CMS-MCP-Dev#512 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 72/100
wimpysworld/sidra#290 ·
维护者通常 1 天内回复
-
defuFn invokes function values for inherited default properties可能已有人在做 @xiehuanyi 今天认领。 未关闭
难度 1/5 1 小时以内 新手友好度 85/100
-
feature request good first issue
难度 2/5 1-3 小时 新手友好度 85/100
TabularisDB/tabularis#853 ·
维护者通常 1 天内回复
-
难度 2/5 1 小时以内 新手友好度 85/100
capricorn86/happy-dom#2474 ·
维护者通常 2 天内回复