幕後花絮

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"

先看會比對到誰,再動手。

第三,也是我認為最重要的一點:把它寫成 hook,不要只寫成規則。

規則是請求,hook 才是保證

同一個坑我踩了兩次。現在我給 Claude Code 的全域規則檔 CLAUDE.md 裡,專門有一節講怎麼停止程序,兩次出事的日期 9 月 7 日和 9 月 14 日都記在裡面。

第二次之後,我不再指望「記住」這件事了。我用 Claude Code 的 PreToolUse hook 寫了一支兩百多行的 Python 腳本:AI 每次要執行 Bash 指令之前,先經過一輪檢查,只要發現 pkill / pgrep 把選項寫在 pattern 後面,就直接擋下來,不讓它執行。

這件事讓我想通了一個差別:

寫在提示詞、規則檔裡的東西,對 AI 來說是「請求」。它大多會遵守,但上下文一長、任務一急,就有可能漏掉。

寫在 hook 裡的東西則是「保證」。它不取決於 AI 記不記得、懂不懂,指令根本到不了 shell。

管理團隊其實也是同樣的道理:重要的事不要靠「大家注意一下」,要靠流程把關。能用 CI 擋的就別靠 code review,能用權限擋的就別靠自律。

現在讓 AI 直接在自己的電腦上執行指令,已經是家常便飯。它犯的錯不見得比人更離譜,但它犯錯的速度比人快得多。給它訂規矩的同時,也記得裝上護欄。

至於我,現在每次看到 pkill,都會反射性地先看一眼參數順序。

兩次全軍覆沒的分頁,換來這麼一個肌肉記憶,也不算太虧。

延伸閱讀