返回文章列表
AI (更新於 2026年6月1日)

我請每個 AI 各住各的房間,然後它們退房不關門——一次我跟 AI 一起收拾爛攤子的過程

上一篇我用 git worktree 把每個 AI 請進獨立房間,續集是它們退房不關門、堆了 24 間殭屍。 但這次我想講的不只是解法——而是「我怎麼跟 AI 一起把它解掉的」: AI 先跟我報錯,我給方向跟安全邊界,它負責寫 hook、改我的交接流程。 整個過程我一行 code 都沒寫,但這仍然是我的解法。

先說一件事:這篇講的 hook,不是我寫的

我最近寫文章的方式變了,所以想先把這件事攤開講。

接下來你會看到一個自動清理 worktree 的 hook、一段改過的交接流程。那些 code 沒有一行是我打的。 我做的事,是在我的 AI 助手(Claude Code)跑來跟我報錯的時候,給它一個明確的方向、幾條不能踩的安全邊界,然後讓它去寫。

我之前的文章很愛炫技術細節——這個 flag、那個 race condition。但說真的,我現在的工作日常根本不長那樣。我的日常是**「我給方向、AI 動手、我們一起踩坑」**。所以從這篇開始,我想誠實地寫這個過程,而不是假裝那些 code 是我一個人在鍵盤前敲出來的。

這次的爛攤子,剛好是個很好的範例。

故事的續集,從 AI 跑來跟我報錯開始

上一篇我講過:兩個 Claude session 共用同一個資料夾會互相蓋掉 commit,解法是 git worktree——給每個 session(和每個會自己 commit 的背景 agent)一個獨立房間。我那時還很得意,結尾寫了句「把每個助手請進它自己的房間」。

問題是,房間沒人退房之後,不會自己消失。

而且我不是自己發現的。是某天我 resume session,畫面開場,我上一篇親手請 AI 幫我寫的那個「雙開警報器」,突然開始對著空氣鬼吼——它跳出一堆警告,說偵測到一堆「正在使用這個資料夾」的東西。可是我手上明明只開了一個 session。

我直接把這個怪象丟給 AI:「你這個警報器在亂叫,幫我查為什麼。」

它查完回報的結論,比警報本身還驚人:

git worktree list | wc -l
# → 24

二十四個 worktree。 全是我之前派出去、用 isolation: "worktree" 跑的隔離 agent,用完沒收的房間。而每一間房門上都掛著一把鎖,鎖裡寫著一個早就死掉的 process id——我那個警報器看到這些鎖,就以為「附近有人在用」,於是開始誤報。

我親手請 AI 打造的防撞護欄,被我自己留下的殘骸搞到失靈。這個發現是 AI 幫我挖出來的,但「這代表什麼、該怎麼辦」是接下來我們要一起想的。

我請 AI 解釋給我聽:為什麼房間不會自己消失

我不想假裝我一開始就懂。我是讓 AI 先把機制講給我聽,我們才有共同的基礎來討論解法。

它的解釋大概是這樣:當你派一個 agent 並指定 isolation: "worktree",harness 會幫它開一個獨立 worktree(.claude/worktrees/agent-<id>),並承諾「如果這個 worktree 沒被改動過,就自動清掉」。

陷阱就在「沒被改動過」六個字。一個真的在幹活的 agent 幾乎一定會改動它的房間——pnpm install 留下整包 node_modules、寫檔、commit。只要房間裡有髒東西,那條「沒改動就自動清」的路徑就不會觸發。房間留了下來,門上掛著鎖:

claude agent agent-<id> (pid 48213)

這把鎖本來是好意——它在說「pid 48213 正在用這間房,別動」。但當 agent 跑完、pid 死掉,鎖卻不會自己解開。它變成一把「鎖著空房」的死鎖。一兩間不痛不癢,但我 isolation 用很兇,半個月就堆出 24 間。

搞懂機制之後,我跟 AI 說:「好,那我們來寫個東西,自動清掉這些死掉的房間。」——然後真正關鍵的部分來了。

我給 AI 的方向:清得乾淨是其次,絕不誤殺是底線

這一段是我覺得「人跟 AI 協作」最有價值的地方。AI 很會寫 code,但**「什麼絕對不能發生」這條線,得我來劃。**

如果只是叫 AI「把所有 agent worktree 清掉」,它大概會很乾脆地寫出最暴力的版本:

# AI 如果只聽到「清掉」,很可能寫出這種——千萬不要
git worktree list | grep agent- | awk '{print $1}' | xargs git worktree remove --force

而我之所以沒有讓它這樣寫,是因為我剛經歷過上一篇那場「commit 人間蒸發」的慘案。我知道一件 AI 不會自動知道的事:

那 24 間裡,不是每一間都是空的。其中可能有一兩間,裡面正住著一個還在跑的 agent——它的 pid 還活著,正在那個房間裡寫 code、commit。一把 --force 剷下去,就是當場毀掉一個正在工作的 AI 的成果。這就是上一篇慘案的另一個版本,只是兇手換成我自己的清理腳本。

所以我給 AI 的不是「怎麼寫」,而是三條底線

  1. 絕不碰任何「還活著」的房間。 寧可少清一間,也不准誤殺一個正在工作的 agent。
  2. 清掉之前,先把房間裡的髒東西救出來。 萬一我判斷錯了,那份改動也得有地方撈回來,不能無聲消失。
  3. 這件事要自動發生,別靠我記得。 我一定會忘。

我給的是「為什麼」跟「不能怎樣」,AI 負責把它翻譯成「怎麼做」。它的實作核心,是一個我覺得很漂亮的小技巧——用 os.kill(pid, 0) 去問作業系統「這個 pid 還活著嗎」:

def pid_alive(pid):
    try:
        os.kill(int(pid), 0)   # signal 0 = 只做存在性檢查,不真的送訊號
        return True
    except (OSError, ValueError):
        return False

對應我那三條底線,它寫出來的判斷是:

for wt in agent_worktrees:                  # 只掃 .claude/worktrees/agent-*
    pid = lock_pid(wt)                       # 從鎖裡撈出 pid

    if pid is not None and pid_alive(pid):
        continue                             # ← 底線 1:還活著的 agent,碰都不碰

    if is_dirty(wt):                         # ← 底線 2:有未提交的改動?
        save(git_diff(wt), f"/tmp/wt-rescue-{name}.patch")   # 先存成 patch 再說

    git("worktree", "remove", wt, "-f", "-f")

我看著這段 code,覺得它精準地命中了我要的東西——這就是好的協作:我守住「不能誤殺」這條線,它把這條線變成 os.kill(pid, 0)/tmp/wt-rescue 這些我自己懶得想的實作。

我們一起踩到的兩個坑

第一版寫好,我請它實際跑一次掃那 24 間。然後我們撞上了第一個坑——而且是 AI 自己先沒注意到、我盯著輸出才發現的。

它回報「清完了」。但我請它把完整的原始輸出貼給我看(這是我跟 AI 工作養成的習慣:別看它的「總結」,看 raw output),結果——

24 間只清掉了 4 間。

剩下 20 間的失敗原因,混在一長串它原本想幫我「精簡掉」的輸出裡:git worktree remove --force(單一 --force對一個「被鎖住」的 worktree 會直接失敗,要用雙重 -f -f,第二個 -f 才是用來輾過鎖的。AI 的總結說「做完了」,但事實藏在它沒強調的那 20 行裡。

這是我跟 AI 工作最重要的一條紀律:它的「總結」是一份方便的摘要,不是事實本身。 要看事實,去讀完整的 raw output。「看起來做完了」是所有 silent failure 最愛的偽裝——不管寫 code 的是人還是 AI。

第二個坑是我提早攔下來的。AI 第二版想「聰明一點」,用 branch 的合併狀態判斷一間房該不該清——「這 branch 被 merge 進 main 了嗎?是的話就清」。我一看就喊停,因為我知道我的 PR 都是 squash merge:squash 之後原本的 commit 根本不是 main 的祖先,--is-ancestor 會回「不是」,於是腳本誤判「這 branch 還活著、別清」,殭屍永遠清不掉。

我跟它說:「別繞 branch 的拓樸,回到最直接的事實——鎖上的 pid 還活著嗎。」這個判斷,是我用對自己 workflow 的了解,幫它修掉了一個它從 code 本身看不出來的盲點。

順手請它改了我的交接流程

清理 hook 搞定後,我還想要一層保險——萬一 hook 還沒輪到觸發、或某間房是這一輪剛開的呢?

我每次 session 收尾都會寫一份「交接文件(handoff)」給下一個 session。我就跟 AI 說:「在我的 handoff 流程裡,加一個收尾檢查,列一次還在的 agent 房間,有殘留就提醒我。」

它就在我的 handoff 流程裡加了一個 Step 0.6:

# handoff 收尾時自動列出殭屍房間
git worktree list | grep '\.claude/worktrees/agent-' || echo "(none — 乾淨)"

於是防線變成兩層:SessionEnd hook 是主力(每次收工自動跑、harness 保證觸發),handoff 的收尾檢查是備援(補 hook 的時間差)。這兩層我都沒寫 code,我只是描述「我要什麼」,AI 把它織進了我既有的流程裡。

三個帶走的教訓(這次更多是關於「怎麼跟 AI 工作」)

1. AI 負責「怎麼做」,但「什麼絕對不能發生」得你來守。 這次的 hook 之所以安全,不是因為 AI 多聰明,是因為我剛踩過「commit 蒸發」的坑、知道「誤殺一個活著的 agent」的代價。我把這條底線講清楚,它才寫得出 pid_alive 那道閘門。你的價值不在打字速度,在判斷力跟那條別人(包括 AI)不會自動知道的紅線。

2. 永遠讀 raw output,別信「總結」——不管寫的是人還是 AI。 AI 回報「清完了」,事實是 24 間只清了 4 間。它不是騙我,是它的摘要天生會省略它覺得不重要的東西。要抓 silent failure,只能自己去讀完整輸出。

3. 把口語的方向,變成會自動執行的東西。 我沒寫 hook、沒寫那個 handoff 步驟。我只是說了「自動清掉死掉的、別碰活的、收尾再檢查一次」。能把一句口語的方向,變成一個會自己跑的 hook,這就是現在跟 AI 協作最爽的地方——紀律不再是「下次我會記得」,而是「系統會幫我做」。


我打算之後的文章都這樣寫:不是「看我寫了多硬核的 code」,而是「看我怎麼跟 AI 一起把一件事做成」。因為這才是我真實的工作方式——我出方向跟判斷,AI 出實作跟速度,我們一起踩坑、一起修。

這篇文章本身,當然也是這樣寫出來的。

(一個小小的閉環:上一篇那個警報器抓到我自己雙開,這一篇換成它被殭屍鎖搞到誤報、逼我請 AI 寫了清理 hook。工具、它製造的問題、還有幫我解決問題的 AI——全都纏在一起,一代追著一代。)