幕后
AI 帮我停个开发服务,顺手把我的浏览器全杀了——两次
POMODIARY 团队 · · 阅读约 2 分钟
9 月 14 号,我的 Chrome 所有标签页同时崩了。
Claude 桌面端、ChatGPT、Obsidian 也一样,同一秒钟全部白屏。
我盯着屏幕愣了几秒,然后意识到:这个场面我见过。一周前,9 月 7 号,一模一样。
凶手是一行看起来很谨慎的命令
两次的起因都一样:我让 AI 帮我把本地的 Next.js 开发服务停掉。它跑了这么一行:
pkill -f "next-server" -U "$(id -u)"
单看这行命令,你甚至会觉得它挺周到的:-f 按完整命令行匹配 next-server,-U 再限定「只杀当前用户的进程」,双保险。
问题就出在这个「双保险」上。
macOS 的 pkill 不会帮你重排参数
Linux 上的 pkill 用的是 GNU getopt,参数顺序随便写,它会自己整理好:选项归选项,pattern 归 pattern。
macOS 上的 pkill 用的是 BSD getopt:碰到第一个不是选项的参数,就停止解析,后面的东西一律当成 pattern。而 pkill 又支持同时传多个 pattern,命中任意一个就算数。
所以这行命令在 macOS 眼里其实是三个 pattern:
- next-server
- -U
- 501(我的用户 ID)
再加上 -f 是按完整命令行匹配,它的真实含义变成了:杀掉所有命令行里带 -U 或 501 的进程。
Chrome、Claude、ChatGPT、Obsidian 这类基于 Chromium / Electron 的应用,每个标签页、每个窗口背后都是一堆 Helper 进程,启动参数又长又杂,非常容易命中。一行命令下去,全部阵亡。
本来想加一层保险,结果这层保险本身成了炸弹。
我后来怎么改的
第一,停开发服务按端口找,不按名字猜。
lsof -ti :3000 -sTCP:LISTEN | xargs kill
谁在监听这个端口就杀谁,不存在误伤。
第二,非用 pkill 不可的话,选项全部写在 pattern 前面,pattern 具体到端口或路径。 动手之前,先用 pgrep 带同样的参数预览一遍:
pgrep -fl "next start -p 3011"
pkill -f "next start -p 3011"
先看会命中谁,再动手。
第三,也是我觉得最重要的一条:把它写成钩子,别只写成规则。
规则是请求,钩子才是保证
同一个坑我踩了两次。现在我给 Claude Code 的全局规则文件 CLAUDE.md 里专门有一节讲停进程,两次出事的日期 9 月 7 号和 9 月 14 号都记在里面。
第二次之后,我不再指望「记住」这件事了。我用 Claude Code 的 PreToolUse 钩子写了个两百多行的 Python 脚本:AI 每次要执行 Bash 命令之前先过一遍检查,只要发现 pkill / pgrep 把选项写在了 pattern 后面,直接拦下来,不让执行。
这件事让我想明白一个区别:
写在提示词、规则文件里的东西,对 AI 来说是「请求」。它大概率会遵守,但上下文一长、任务一急,就有漏掉的可能。
写在钩子里的东西是「保证」。它不依赖 AI 记不记得、理不理解,命令根本到不了 shell。
管理团队其实也是一个道理:重要的事情不要靠「大家注意一下」,要靠流程卡住。能用 CI 拦的别靠 code review,能用权限拦的别靠自觉。
现在让 AI 直接在自己电脑上跑命令已经是日常了。它犯的错不见得比人更离谱,但它犯错的速度比人快得多。给它立规矩的同时,记得装护栏。
至于我,现在每次看到 pkill,都会条件反射地先看一眼参数顺序。
两次全军覆没的标签页,换来这么一个肌肉记忆,也不算太亏。
继续阅读
- 我把小时候看蚂蚁搬虫子,做成了 20 关游戏
Crumb Colony 第六章「草丛野味」:20 只蚂蚁真正会搬的虫子。这一章的关键盒子常埋在深处,光靠推理推不出来,但每一关都保证不用道具也能通关。
- 一个 10 万+ 下载的老 App,沉寂两年,翻新 13 天后又开始赚钱了
番茄打卡在 Google Play 有 10 万+ 下载,却一两年没有像样的更新。13 天合并 53 个 PR 翻新之后,9 月底起又陆续来了订单。
- 我做了个小游戏:看一群蚂蚁,把像素画一口一口搬回家
Crumb Colony 是一个网页像素解谜小游戏:放出同色蚂蚁,看它们把像素画一口一口搬回蚁穴。20 道像素美食,每一关都确认有解。
