Cholate
← 回首頁

2026-07-30 · 5 min · multi-agent-starter 開發記事(第 10 / 11 篇)

上線五天的決策,被一場 rebase 掀翻

全機隊剛決定不讓設定目錄進 git,五天後整個翻轉。導火線是一場把檔案從磁碟上靜默抹掉的 rebase。不可重建性,排在整潔前面。

一個邏輯嚴密的決策#

11 個專案,誰在燒錢?說到機隊治理。這一篇講治理裡最痛的一課。

問題很單純:kit 鋪進每個專案的 .claude/ 目錄(規則、hooks、設定),要不要進各專案的 git?

7 月 5 日,我做了全機隊決策:一律 gitignore。邏輯很漂亮,因為這些檔案的真理源在 kit repo,隨時可以 --update 重鋪,何必讓 11 個 repo 各揹一份副本、每次 kit 升級都產生一堆無意義的 diff 噪音?決策乾脆,執行也乾脆,AI 當天就給六個專案 untrack 完畢(untrack=請 git 從此別追蹤這些檔案;檔案還留在磁碟上,只是 git 不再管它們的死活——這半句待會是伏筆)。

五天後,這個決策被整個翻轉成一律 track。中間發生了什麼,值得從機制講起。

rebase 把檔案從磁碟上抹掉了#

事故發生在部署當天。其中一個專案 untrack .claude/ 之後要 push,被一個爬蟲 bot 的自動 commit 擋下,於是走 pull --rebase。標準操作,做過一萬次的那種。

然後,剛 untrack 的 .claude 檔案消失了。沒有警告,沒有 conflict,就是不見了:settings.json 和三個 hook 檔直接從磁碟蒸發。

機制值得每個用 git 的人記一筆。事故報告(LESSONS)的 Error 欄原文是這樣開頭的:

rebase checkout origin/main 時,gitignored 的 untracked .claude 檔被舊 tracked 版本靜默覆蓋(gitignored 路徑不受 checkout 的 untracked-file 保護)

拆開來講:git 平常會保護 untracked 檔案不被 checkout 輾過,但這層保護不涵蓋 gitignored 的路徑。你都聲明不在乎它了,git 就真的不在乎它。於是 rebase 過程 checkout 遠端版本時,遠端還記得這些路徑的舊 tracked 版本,直接蓋過磁碟上的現行檔;接著 replay 到 untrack 那個 commit,路徑從追蹤清單消失,但內容早被舊版覆蓋殆盡。

「untrack+gitignore」再疊一個 rebase,是一台安靜的碎紙機。

修復本身不難:重跑 --update 補回 kit 鋪的部分,再逐檔對 kit repo 比對完整性。可怕的不是這次的損失,而是它示範了另一批檔案的死法會有多安靜。這就要說到翻轉的真正論證了。

事故重排了價值的順序#

「gitignore 派」的前提是「這些檔案都可以重建」。事故逼著把這個前提逐檔重驗,結果它只對了一半。kit 鋪的規則和 hooks 確實可重建,--update 一下就回來;但同一個目錄裡還住著兩種東西:

  • settings.json 的權限核可清單:一條一條人工按過「允許」攢出來的,最大的專案累積了 26 條;
  • protected-paths 的專案禁區:每一行都是一次「這裡不准 AI 碰」的判斷。

這些是專案自己的真理,kit repo 裡沒有、重建不出來。gitignore 等於讓它們只存在一顆硬碟上,而那顆硬碟剛剛示範了內容可以怎樣無聲消失。

原決策優化的是「避免 repo 噪音」;事故把價值重新排序:不可重建性 > 整潔。兩個價值其實從第一天就都在桌上,只是沒有事故逼你選邊站的時候,你會下意識選好看的那個。五天前的決策不是蠢,是還沒付過學費。推翻自己不可恥,可恥的是付了學費還為了面子繼續錯下去。

值得補一句:翻轉之後,原決策怕的那個噪音還是真的。kit 每次升級,11 個 repo 照樣各長出一批規則檔的 diff。它沒有被解決,是被接受了:跟「專案真理只存在一顆硬碟上」相比,diff 噪音是便宜得多的代價。好決策不是消滅所有成本,是選對要付哪一種。

這次翻轉也回收了一條舊線。這個系列前面講過「所有權從段落升格為檔案」,kit-owned 與 user-owned 的二分;gitignore 事件證明這個二分不只管「誰能改」,還管**「誰死得起」**。kit-owned 死得起(可重鋪),user-owned 死不起(不可重建)。死不起的東西,必須進版本控制。

執行也踩了一個小坑#

翻轉執行時附贈一枚地雷:git add .claude 把目錄整包收進去,其中一個專案裡撈到一顆五月殘留的排程鎖檔。內容是 pid 和 session id,對別台機器毫無意義的執行期狀態,就這樣被 commit 進了歷史。

最終規則因此多了一行精度:track 目錄本體,只排除機器本地狀態。這段規則現在活在 kit 的 gitignore 模板末尾,連同整個事件的論證一起(模板原文):

# Claude Code runtime state. `.claude/` itself IS tracked on purpose:
# kit-owned files are regenerable via `init.sh --update`, but settings.json
# (accumulated permission approvals) and protected-paths (project no-touch
# zones) are project truth that nothing can rebuild. Only machine-local
# state is excluded.
.claude/settings.local.json
.claude/scheduled_tasks.lock

註解比排除規則本身長三倍,而且是刻意的:下一個看到這兩行的人(或 AI)不用重新吵一遍「為什麼 track」,論證就寫在現場。教訓一句話:把一個目錄整包納入版控之前,先列出它的實際內容,逐檔問「這在另一台機器上還有意義嗎」。工具目錄裡混著設定、產物與執行期狀態,git add 不會替你分辨。

順帶一提:事故發生在 7 月 5 日,翻轉的 commit 在 7 月 10 日傍晚。這五天的猶豫在任何紀錄裡都看不到。git 只記得你做了什麼,不記得你掙扎了多久。

下一篇#

這一篇記的是一次五天的自我推翻:搞懂了 rebase 碎紙機的機制(gitignored 路徑不受 checkout 保護),換來一次價值重排(不可重建性大於整潔),還順手得到一把新尺:檔案的分類不只看「誰能改」,還要看「誰死得起」。

同一個 7 月 10 日,翻轉 gitignore 的幾個小時前,另一條戰線已經開打。防線蓋好之後,開始有人抱怨它擋到不該擋的:幾行的小改被逼跑完整 review、越認真補測試越容易被懲罰。最後一篇,講 review 的經濟學:3 個小需求、6 輪 review、87 分鐘


#claude-code