多 Agent 并行后,如何定位残留进程
文章从 Claude Code 会话结束后开发服务器、测试任务仍驻留的问题切入,介绍定位待终止进程的命令。多工作树并行会放大资源占用,直接批量杀死 Node 进程可能误伤其他 Agent 的任务。
AI 深度解读
多开几个 AI 编程代理,任务结束后,电脑却还在发热:留下来的可能是开发服务器、测试监听器和构建进程。原文给出八组 macOS、Linux 排查命令,重点是把资源占用追溯到具体任务,再决定该结束哪一组进程。
对同时跑 Claude Code、Codex 和多个 worktree 的开发者,这套方法很实用。但命令只能告诉你「谁还活着」,不能替你判断「谁已经没用了」。后一个问题,才是避免误杀的关键。
先找资源大户,别看到 node 就动手
原文引用了两份问题报告。2026 年 4 月的 Claude Code issue #43944 称,Bash 工具启动的进程会在会话退出后继续运行,孤立的 next dev 可能涨到 8+ GB;该 issue 在 6 月因不活跃关闭。更早的 #16198 则记录了一个未停止的 wrangler dev 所启动的 workerd:约两天后仍占用 89.1% CPU、约 2.5 GB 内存。
这些是用户报告,能说明故障出现过,不能推导出发生概率。作者设想的放大场景是五个代理并行,每个都可能启动开发服务器、测试监听器、类型检查器和端到端测试浏览器。
第一步是看清 PID、父进程、进程组、内存和存活时间。Linux 用下面的命令,按 CPU 排出前面的记录:
ps -eo pid,ppid,pgid,%cpu,rss,etime,args \
--sort=-%cpu | head -15
重点看 pgid,后面清理整组进程要用它。macOS 没有 --sort,对应命令是 ps -Ao pid,ppid,pgid,%cpu,rss,etime,command -r | head -15,按内存排序则用 -m。
rss 在两端都是 KiB,2500000 约为 2.4 GB;etime 的格式为 [[DD-]hh:]mm:ss,2-03:10:44 表示已经运行两天多。一个比当前所有代理会话都老的开发服务器,值得追查。
PPID=1 也只是线索。macOS 的 launchd 本来就是许多正常应用和守护进程的父进程;Linux 的孤儿进程还可能被通过 PR_SET_CHILD_SUBREAPER 注册的祖先进程接管,PPID 未必变成 1。接管会改变 PPID,PGID 则保持原值。
两端的 %cpu 也不同:Linux ps 给的是整个存活期的平均值,macOS 给的是最长约一分钟的衰减平均。老进程刚开始忙,Linux ps 可能看起来仍很低,此时应看 top。
内存排序分别用 top -o mem 和 Linux 的 top -o %MEM。macOS 的 mem 是物理内存占用口径,还可用 top -l 1 -o mem -n 10 -stats pid,pgrp,command,mem 获取带进程组的一次快照。Linux 中按 P、M 可切换 CPU、内存排序。
想交互查看树形结构,可以运行 htop -t -F node:F4 改过滤条件,F5 切换树形,F9 打开信号菜单。-F 是忽略大小写的字面匹配,可用 | 分隔多个词,但只搜索命令行。
怎么把一个进程认回它所属的任务
进程名只能告诉你它是什么,任务路径、端口和进程组才能帮助确认它属于谁。
Linux 的 pstree -pga 48211 会从指定 PID 展示子树,包含 PID、PGID 和参数;ps -ef --forest | less 则展示全系统父子关系。前者通常来自 psmisc,多数 Linux 进程工具来自 procps-ng。
macOS 默认没有这两个树形查看方式。brew install pstree 安装的是 Fred Hucht 的实现,原文所述 Homebrew 版本为 2.40:用 pstree -s fix-login 匹配命令行分支,用 pstree -p 48211 找包含该 PID 的分支。这里的 -g n 选择绘图字符集,不能照搬 Linux 的进程组参数。
树也有盲区:父进程退出、子进程被接管后,原来的归属链就断了。

第二条线索是 worktree 路径。Linux 用 pgrep -af "$HOME/code/wt/fix-login",macOS 用 pgrep -lf "$HOME/code/wt/fix-login"。-f 搜索完整参数,模式是扩展正则表达式。macOS 的 -a 表示包含祖先进程,与 Linux 打印完整命令行的含义不同。
但在任务目录执行的 npm run dev,参数里可能根本没有目录路径。这时要用 lsof +d ~/code/wt/fix-login 找该目录中打开的文件,输出中的 FD=cwd 表示工作目录,txt 表示程序可执行文件。
+d 只看顶层;lsof +D ~/code/wt/fix-login 递归整个目录,可能耗时且占用大量内存,包含完整 node_modules 时尤其如此。两者默认都不跟随符号链接,需要时加 -x。
第三条线索是端口:lsof -iTCP -sTCP:LISTEN -nP 列出 TCP 监听者,lsof -nP -iTCP:3000 -sTCP:LISTEN 限定端口。-n 跳过主机名解析,-P 保留数字端口。
如果 3000、3001 都有 node 监听,而后者对应任务已经结束,就可以继续追查。脚本里可用 lsof -t -iTCP:3001 -sTCP:LISTEN 获取 PID,再用 ps -o pgid= -p <pid> 查询进程组;实际使用仍应检查是否返回多个 PID。
清理要按组执行,效果要看当前压力
确认归属后,原文建议先列出组成员,再发信号。这组命令解决的是只结束 npm 父进程、留下 Node 和 worker 的问题:
pgrep -l -g 48211 # macOS: who is in the group
pgrep -a -g 48211 # Linux
kill -- -48211 # SIGTERM to every member
sleep 5
pgrep -g 48211 && kill -KILL -- -48211
最需要确认的是前两行的成员列表。如果代理自身也在组里,它也会收到信号。负 PID 指定进程组,-- 用来结束选项解析;POSIX 给出的例子是 kill -- -123。
默认的 SIGTERM 允许进程执行清理,等五秒后再对仍存在的组发送 KILL。pkill -g 48211 也能按组发信号。不过,主动创建了新进程组的子进程会漏掉,所以「杀组」也不等于必然覆盖整棵树。
最后要验证资源是否恢复。macOS 用 vm_stat 5 看首轮累计值和后续变化,重点关注 swapouts 是否持续增加;sysctl vm.swapusage 给出交换空间的 total、used、free。已有 swap 占用只说明过去发生过内存压力,不能证明现在仍在换页。
Linux 用 free -h,或 free -h -s 5 每五秒观察一次,重点读 available,即估计无需交换就能供新应用使用的内存。清理前后变化不明显,应该回到进程列表继续查;单凭一个数字,也不足以断定清理失败。
我的判断
原文最有用的是把排查串成了闭环:找占用、认任务、查组成员、发信号、看压力。它比按名称批量结束 node 更适合多 worktree 场景,也比只盯父子树更能应对孤儿进程。
但它没有提供清理前后的对照数据,也没有证明多代理通常会留下多少进程。这是一套结合问题报告和命令语义的操作经验,不能当作性能收益承诺。
另一个前提是任务与进程组之间的对应关系足够清晰。worktree 隔离了文件和分支,不会自动保证所有相关进程都在独立进程组里;端口也只能辅助确认。
作者还推广了自己开发的 Hermes IDE,需要把这层利益关系放进判断。按原文介绍,它为会话显示 working、needs approval、asked you、done、error、idle,并标注推测状态;每项任务拥有独立分支、worktree,新 worktree 还分配端口。但退出 Hermes 不会停止代理,恢复连接正是它的设计之一。
任务显示 done 或 idle,只代表可以检查清理,不能直接作为自动结束进程的依据。
needs approval、asked you 更可能是任务等人回应。相比依赖某个 IDE,小团队也可以先维护任务目录、端口和启动进程的对应记录。原文没有给出两种方式的效率对比。Hermes 采用 BSL 1.1、提供源码并支持 macOS、Windows、Linux;本文命令只覆盖前两者。
能拿来做什么
- 同时跑多个前端任务时,为每个 worktree 记录端口和任务状态。发现多个
next dev后,先用端口、目录交叉确认归属。 - 排查 Node 开发环境发热时,先看
top,再查etime、PGID;路径搜索没有结果,就用lsof +d补查工作目录。 - 清理已结束的 AI 编程任务时,先人工核对进程组成员,再按
SIGTERM、等待、KILL的顺序处理,最后观察当前内存压力。 - 如果准备把命令写成清理脚本,先让脚本只输出候选任务、PID、PGID 和成员列表。等归属能可靠确认后,再加入结束进程的动作。