假設你在同一個儲存庫開了兩個 Claude Code,一個修測試,一個改文件,而且你在 prompt 裡交代過:只 commit 自己改的檔案。結果改文件的那個打了 git commit,把修測試那個剛 stage 好的檔案也一起帶走了。
叫 agent 小心沒有用,因為那條規則只是畫面上一段字,session 一忙就會略過。要擋住這件事,得換成一個會拒絕的檢查。hook 本身只要幾行 shell,難的是它需要一個 git 沒有記的答案:這個 staged 的路徑,到底是誰的工作。
兩個視窗各自 git add,進的是同一份 index
git 一個工作目錄只有一份 index,而 index 就是一個檔案。兩個 session 共用一份 checkout,就是兩個都在寫這個檔案。它沒有主人,這邊 git add 和那邊 git add 進的是同一份暫存區。接著任何一邊打一個沒帶路徑的 git commit,就把裡面全部提交出去,兩邊都不會收到任何提醒。
這不是什麼特殊用法。你在同一個儲存庫開到第三、第四個 session,自然就是這個樣子。
叫 agent 小心一點,我們自己一個星期破了三次
我們自己的儲存庫裡就有這條規則,寫得清清楚楚,結果一個星期內被打破三次。2026 年 8 月底,有兩次是一個沒帶路徑的 git commit,把另一個 session 已經 stage 好的檔案一起帶走。六天後更嚴重:一個 git reset --hard HEAD,把另一條工作線正在進行的合併整個毀掉,包括一個 MERGE_HEAD、六個手動解好的衝突,還有一個 2,265 行的新檔案。
三次都一樣:規則在、檢查也跑了、輸出也正確,然後它被略過了。
規則本身什麼都不缺。缺的是一個能執行它的東西;在那之前,指示就只是建議。
不靠任何服務的那一半,九行就能拒絕
先處理你自己就能做的那一半。如果 git 目錄裡有 MERGE_HEAD、CHERRY_PICK_HEAD、REVERT_HEAD 或 rebase 的狀態目錄,代表有一個操作還沒做完,你現在 git commit 就是在替它收尾。在共用的 checkout 裡,開始那個操作的不一定是你。檢查這件事只要九行,不需要任何基礎設施:
#!/bin/sh
# pre-commit——拒絕替一個「這個 session 可能沒有開始過」的操作收尾。
git_dir=$(git rev-parse --git-dir) || exit 0
for f in MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD rebase-merge rebase-apply; do
[ -e "$git_dir/$f" ] || continue
echo "pre-commit: $f 存在——這次 commit 會替一個進行中的操作收尾。" >&2
echo " 如果那確實是你的:git commit --no-verify" >&2
exit 1
done
它只讀本機的 git 目錄,沒有任何依賴會掛掉。注意它用 git rev-parse --git-dir 找 git 目錄,沒有寫死 .git,因為在 linked worktree 裡,那些狀態檔不在 .git 底下。
訊息裡特地寫出 git commit --no-verify。守衛如果完全沒有通道可以繞過,大家就會乾脆把它拔掉,從此再也不用。--no-verify 跳得過 pre-commit,跳不過 post-commit,所以你可以在 post-commit 印一行,說這次 commit 沒有經過檢查。
「這個檔案是誰改的」,git 從來沒記
另一半,你在 git 裡面解決不了:這個 staged 路徑是不是別人的工作?git 記的是 index 裡有什麼,從來不記是誰放進去的。所以答案必須在 commit 之前就存在,由派工作出去的那一方先寫下來。
這正是 Clawdline 負責的部分。每個派出去的任務都要宣告它的寫入範圍(claims),也就是它可以改哪些儲存庫路徑;範圍是空的,就是明確的唯讀任務。我們的 pre-commit hook 會問本機的 App 目前有哪些任務還在跑、各自宣告了哪些路徑,只要一次 commit stage 到別條工作線的路徑,就拒絕它。
路徑屬於工作線,不屬於視窗。派出任務的那個 session,可以提交它派出去的任務宣告過的路徑,而且合併收尾的時候它非這樣不可。
這兩個檢查失敗的方向,是我們刻意設計成相反的。檢查進行中操作的那一個會拒絕,因為它什麼都不依賴。檢查路徑歸屬的那一個要去問一個不一定開著的服務,所以它只警告:列出它沒辦法背書的路徑,然後放行。
一個什麼都不依賴的檢查可以拒絕;一個要靠某個服務活著的檢查不可以拒絕,不然那個服務一停,你就跟著不能工作。
被擋下來時,先把別人的路徑拿出暫存區
被擋下來,處理方式是兩個指令:
git reset -- <對方的路徑> # 只把它從暫存區拿掉,不碰它的內容
git commit -m "<訊息>" # 然後提交你自己看過的 index
還有第三個指令看起來最對,其實是陷阱:git commit -- <你的路徑>。它提交的是這些路徑在工作目錄裡的內容,不是你 stage 起來的那幾段 hunk,所以另一個 session 還沒 stage 的改動,也可能被一起帶進去。
hook 只在 git commit 時跑,最嚴重那次它擋不到
這個守衛管得很窄,因為 pre-commit 只有在 git commit 的時候才會跑。拿我們自己那三次來對照:
| 操作 | pre-commit 會跑嗎 |
我們自己那三次 |
|---|---|---|
git commit |
會。只有這一個會呼叫它 | 8 月那兩次。擋得住 |
git commit --amend |
會跑,但 HEAD 裡原本就有的東西直接過去,不受檢查 |
– |
git reset --hard |
不會 | 9 月那一次。擋不住 |
git stash |
不會 | – |
沒有衝突的 git merge |
不會。它直接寫下 commit,完全不呼叫 hook | – |
merge 那一列,很多人以為答案相反,我們在 git 2.38.1 上確認過。結果就是:這個守衛擋得住三次裡的兩次,擋不住最嚴重的那一次。reset --hard 永遠不會經過 pre-commit,而 reference-transaction 要等工作目錄和 index 都已經沒了才會觸發。
要不要乾脆每個 session 開一個 worktree?
git worktree add 會給 session 一份自己的工作目錄和自己的 index,上面這整類問題就不會發生。
但這是取捨,不是升級。新開的 checkout 沒有 build 快取,最開始幾分鐘都花在重新編譯原本就在磁碟上的東西。2026 年 8 月 28 日,我們在自己的一台機器上量了 138 個派出去的任務:跑在獨立 worktree 的,每秒用掉的 token 大約是 1.5 倍,中位數約 8,200 對上暖好的共用 checkout 的 5,400。只是一台機器、一個人的習慣,但方向很明確。
所以我們的經驗法則是:會動到很多檔案的長工作,放進自己的 worktree;短工作留在共用的 checkout,前面擋一個守衛。
兩種做法都顧不到的事
第一種是大家一起往裡面加內容的共用檔案。兩個 session 改的是不同行,git 不會警告,某一邊還沒 commit 的內容,就會跟著下一個提交這個檔案的人進去。我們自己的檔案在 2026 年 9 月 3 日就這樣出過事。提交這種檔案之前,先用 git diff --cached -- <檔案> 看每一段 hunk,因為 stat 只告訴你改了幾行,不會告訴你是誰的。
第二種是坐在那台機器前面、也在改同一批檔案的人。兩種做法都看不到這個人,但那個什麼都不依賴的檢查還是擋得住,因為它讀的是 git 目錄,不管你是誰。
回到開頭那兩個視窗。它們還是共用同一份 index,但裝了那九行 hook,其中一個再想替別人進行中的合併收尾,就會先被擋下來;要擋住它帶走別人 stage 好的檔案,還得有人在 commit 之前寫下哪條路徑是誰的。一個任務會宣告什麼、之後由誰負責,寫在工作流程指南;這些名詞的定義在 Stations 那一頁。今晚只帶一樣東西回你自己的儲存庫:那九行 hook。