一条 pkill 关掉了我电脑上所有的应用
几个子 agent 并行干活时,其中一个想结束一个卡住的进程,结果一条命令把电脑上几乎所有应用都关了。记录一下经过和防范的办法。
前几天晚上,我开着好几个 Claude Code 会话在跑任务,其中一个会话里还同时跑着几个子 agent,在做一个比较长的工程任务。正跑着,电脑上的应用突然一下全关了。Chrome、终端,还有其他所有的 Claude Code 会话,全没了,只剩那个跑着子 agent 的会话还在。
一开始以为是系统崩了或者内存爆了,但电脑并没有重启,内存也一直很宽裕。
怎么查到的
我马上重新打开终端,起了一个新的 Claude Code 会话,让它去查刚才到底发生了什么。Claude Code 会把每个会话的操作记录存在本地,子 agent 执行过的每条命令也都在里面。它翻了一遍,很快就找到了:出事的那一刻,有一个子 agent 执行了这样一条命令:
pkill -f "cat" -n它为什么要执行这条命令
再往前看一点就清楚了。这个子 agent 前面写文件的时候,有一条命令写得不完整,留下了一个一直在等输入的 cat 进程,整条命令就卡在那儿了。它发现卡住之后,想把这个 cat 结束掉,于是写了上面这条 pkill。它的本意是"结束最新的那个 cat",-n 就是"只要最新那一个"的意思。
为什么会关掉所有东西
问题出在写法上。在 macOS 上,pkill 的选项必须写在要匹配的名字前面。-n 写在了 "cat" 后面,系统就不再把它当成选项,而是当成了第二个要匹配的词。"只结束最新的一个"这个限制,就这么没了。
再加上 -f 的意思是拿进程的完整路径和参数去匹配,而不只看进程名。"cat" 这三个字母太常见了,Applications 这个单词里就有(Applications),几乎所有装在"应用程序"文件夹里的软件,路径里都带着它。我的 Claude Code 会话是从一个装在那里的终端软件里启动的,启动参数里也带着这个路径,所以也一起被匹配上了。
结果就是,一条本来只想关掉一个小进程的命令,把电脑上大部分应用都关了。那个跑子 agent 的会话能活下来,是因为 pkill 不会结束发出命令的进程和它的上级进程,而子 agent 就运行在那个会话里面。
这不是偶然
其实九月底我就遇到过一次,原因一模一样,也是选项写在了名字后面,那次一下关掉了十几个会话。之后我专门让 Claude 把"不要用 pkill"记进了它的记忆和项目规范里,但这次还是发生了。规范只是写给模型看的提示,子 agent 不一定读得到,读到了也不一定照做。
网上也能找到不少类似的经历。Claude Code 的 GitHub 上有人反馈,它为了停掉一个 Node 服务执行了 pkill node,结果把 Claude Code 自己和 VS Code 一起关掉了(#3068)。还有人在 Windows 上让它结束一个 Python 进程,它直接把所有 Python 进程都结束了,跑了很久的模拟计算全没了。这个用户专门提醒过、还列出了不许动的进程,结果又发生了第二次(#34754)。
这种事对长时间的任务影响挺大的。几个 agent 并行跑了一两个小时,一条命令下去全部中断,之后还得一个个恢复会话,检查每个做到了哪一步。
怎么预防
- 结束进程的时候,按进程编号(PID)或者端口来,不要按名字去匹配。比如要停一个开发服务器,先用端口查出它的 PID,再只结束这一个。
- 如果一定要按名字匹配,先用
pgrep -lf 名字看看会匹配到哪些进程,确认没问题再动手,并且选项一律写在名字前面。 - 不要只靠提示词,在执行这一层直接拦。Claude Code 支持 hook,可以在每条命令真正执行之前先检查一遍,不符合规则就直接拒绝,模型也绕不过去。我和 Claude 一起做了一个小插件,命令里只要出现 pkill 或者 killall 就拒绝执行,同时告诉模型应该改用什么方式。
这个插件其实之前就做好了,但当时只在开发它的那个会话里生效,别的会话和子 agent 都没加载上。这次重新装到了全局,测了一下,主会话和子 agent 再用这类命令都会被拦下来。
说到底,这只是一个参数位置写错的小问题。但一台电脑上同时跑着很多个 agent 的时候,一条命令写错,影响的就是整台电脑。所以这类规则,还是放在执行这一层去拦更靠谱。