跳到內容

第 5 章 多 session 多工

一句話:一件事開一個 session、一張自己的桌子,做完把成果搬回主桌。

終端機 Aworktree a(分支 worktree-a)只改 src/ 與 tests/終端機 Bworktree b(分支 worktree-b)只改 README.md兩件事同時進行,各用一份自己的目錄影本終端機 C:主 checkoutmain 分支,最後由你收斂git merge --no-ff --no-editworktree-agit merge --no-ff --no-editworktree-b
一件事一張桌子:A、B 各自在自己的影本上做,做完由你在主桌把成果合併回來。

一個 Claude Code session 就是一段獨立的對話加一個工作目錄。把兩件不相干的事塞進同一個 session,會發生兩件壞事:

  • 互相污染:前一件事的檔案、討論、決定全都留在對話裡(第 2 章講過的 context 成本),第二件事的品質跟著下滑。
  • 互相排隊:第二件事要等第一件做完才能開始。

多 session 多工就是「一件事一個 session」,讓它們同時進行。難點不在開很多視窗,而在不要互相踩檔。 claude --worktree 解決踩檔:它用 git worktree 幫每個 session 開一份獨立的目錄影本與分支,各做各的,最後由你在主 checkout(你平常工作的那份目錄)把成果 merge 回來——這一章就在練這件事。走 Orca 路線的話,同一件事由它的 worktree 面板代勞:每個任務天生就是一個 worktree,本章正是 Orca 整個產品圍繞的工作模式。

原理:為什麼同一個目錄會互相蓋檔

Section titled “原理:為什麼同一個目錄會互相蓋檔”
時間 →session Asession B讀 todo.py寫入:加了 --json此刻檔案是 A 的版本讀 todo.py讀到的還是舊版寫入自己的版本A 的 --json 消失了
兩個 session 看著同一份檔案:後寫的人永遠贏,先寫的人的改動就默默消失——而且 git 也分不出是誰改的。

蓋檔不是 Claude 的 bug,是「兩個人看著同一份檔案」的必然結果。上圖的時間線:A 先寫入了加好 --json 的版本;但 B 手上拿的還是更早讀到的舊版,等 B 寫入時,就把 A 的成果整段蓋掉——B 不知道 A 改過,git 也只看到「檔案被改了」,分不出是誰的手筆。

worktree 的解法是讓兩個人根本不看同一份檔案。開兩個 worktree 之後,目錄長這樣:

claude-code-practice/ ← 主 checkout(main 分支)
├── src/todo.py
└── .claude/worktrees/
├── a/ ← session A 的影本(worktree-a 分支)
│ └── src/todo.py
└── b/ ← session B 的影本(worktree-b 分支)
└── src/todo.py

三份 todo.py 各自獨立,A、B 怎麼寫都不會碰到彼此;每份影本各在自己的分支上 commit,最後由你回主 checkout 用 merge 把兩條分支的成果有紀錄地合起來——衝突了 git 會告訴你,而不是默默消失。(上圖的目錄是 CLI 路線的樣子;Orca 把 worktree 放在它自己管理的位置、分支名是任務名——目錄位置不同,隔離的原理完全一樣。)

要加一個 CLI 選項、同時要補 README 的使用範例。兩件事碰的檔案完全不重疊,一個 session 做完再做另一個純粹是在排隊。開兩個 worktree session,各自派一件,時間直接砍半。

情境 B:一邊跑長任務、一邊改別的

Section titled “情境 B:一邊跑長任務、一邊改別的”

一個 session 正在跑一個要十幾分鐘的重構或測試修復,你不想在旁邊乾等。開第二個 worktree session 處理另一件小事,長任務那邊不會被你的新提示詞打斷,也不會看到你正在改的檔。

情境 C:一個 session 看 log、一個改 code

Section titled “情境 C:一個 session 看 log、一個改 code”

排查問題時,一個 session 專門讀 log、整理線索、給你結論;另一個 session 拿著結論改 code。兩邊的 context 各自乾淨:讀 log 的那個不會被程式碼的討論淹沒,改 code 的那個也不用背著幾萬 token 的 log。

情境 D:一個寫、一個審(Writer/Reviewer)

Section titled “情境 D:一個寫、一個審(Writer/Reviewer)”

session A 實作一個功能;開 session B(乾淨的 context)請它審 A 的產出——B 沒看過 A 的實作過程,不會替那些決定護短,審起來更像另一位工程師。把 B 的回饋貼回 A 修。測試也能這樣玩:一個 session 寫測試、另一個 session 寫實作去通過它。這是第 2 章「對抗式審查」的 session 版,隔離更徹底。

反例:同一個 checkout 開兩個 session 改同一批檔

Section titled “反例:同一個 checkout 開兩個 session 改同一批檔”

在同一個目錄開兩個 claude,各自要求改 src/todo.py。兩個 session 看到的是同一份檔案,A 剛寫進去的內容 B 下一秒就覆蓋掉,git status 也分不出誰改了什麼。這不是多工,是互相破壞(見「原理」的時間線)。真的要動同一批檔,就排隊,或先拆成不重疊的工作。

Terminal window
claude --worktree <name>

Claude Code 會在 repo 根目錄下建 .claude/worktrees/<name>/(一份獨立的工作目錄),並開一條新分支 worktree-<name>,然後在那個目錄裡啟動 session。-w 是同一件事的縮寫。 同名再開一次,會直接進既有的 worktree(自己先用 git worktree add .claude/worktrees/<name> -b worktree-<name> <起點> 開好的也算)。它預設從 origin/main(GitHub 上那份 main 在你電腦裡的複本)長出來,所以每個 worktree 的起點都是乾淨的——反過來說,只在本機 commit、還沒 push 的東西不會出現在新 worktree 裡。期末作業會碰到這件事,到時用 settings.json 的 worktree.baseRef 讓它改從本機 HEAD(=你目前所在的那個 commit,也就是本機 main 最新的一個)長。

給每個 session 的提示詞要自足:做什麼、只准碰哪些檔、做完要 commit 還是不要。session 之間看不到彼此的對話,所以「不要動 README」這種邊界一定要寫進提示詞,不能靠它自己猜。

每個 session 收尾時請它用一句話摘要做了什麼、commit 了哪些檔。然後你回主 checkout(不是任何一個 worktree)做 merge:

Terminal window
git merge --no-ff --no-edit worktree-a
git merge --no-ff --no-edit worktree-b

--no-ff 讓每個 session 的成果在 git log --graph 上各自成一個 merge 節點,事後看得出來哪批改動來自誰(ff=fast-forward:main 沒有新 commit 時,git 預設直接把指標挪到分支尾端、不留合併紀錄)。--no-edit=用 git 自動產生的合併訊息——沒加的話 git 會開編輯器(多半是 vim)要你確認訊息,不熟 vim 的人會卡在那個畫面。

在 worktree session 打 /exit,如果裡面有還沒 merge 出去的東西(未 commit 的改動、或新的 commit),Claude Code 會問要 keep 還是 remove:

  • keep:保留目錄與分支,之後 claude --worktree <同名> 可以回來繼續。
  • remove:刪掉目錄與分支,裡面沒 merge 出去的改動一起消失。

規則很簡單:已經 merge 回主 checkout 的選 remove;還沒的選 keep。

一個團隊的後端開發流程被寫成一條五站的 workflow(第 3 章實務案例那條:ROUTE → SPEC → BUILD → VERIFY → DONE),SPEC 站硬停等人確認才往下走。這種長流程一跑就是幾十分鐘,工程師的做法是:主 checkout 留給自己看 diff 與回答 SPEC 站的問題,另外開一個 --worktree session 處理不相干的小修——長流程不受打擾,小修也不用等。

進階選做:無頭模式的批次 fan-out

Section titled “進階選做:無頭模式的批次 fan-out”

平行的極端形態是不開互動介面:claude -p "提示詞" 直接跑完就退出。大批的機械式改動(幾百個檔案的遷移、逐檔加型別)可以用迴圈發出去:

Terminal window
for f in $(cat files.txt); do
claude -p "把 $f 從 Python 2 遷移到 Python 3,完成回 OK、失敗回 FAIL" \
--allowedTools "Edit,Bash(git commit *)"
done

--allowedTools 限住它能用的工具——無人看管時這很重要。訣竅是先拿兩三個檔試跑、照失敗情況改提示詞,再跑全部。細節見官方 headless 文件。

練習兩個 worktree 分工,第三個終端機收斂

  1. 起點:

    第一次做就 clone;已經 clone 過、或要重做這一章,用第二段把前一次留下的 worktree、分支與 merge 結果清掉。已經在 repo 目錄裡的人不要再貼 clone 這段。

    Terminal window
    git clone https://github.com/AngusLu0731/claude-code-practice.git
    cd claude-code-practice

    已 clone/重跑(在 repo 根目錄執行;只動 a、b 這兩個 worktree 與同名分支,不碰你另外開的 worktree;可以重複跑):

    Terminal window
    for n in a b; do
    git worktree remove --force ".claude/worktrees/$n" 2>/dev/null
    git branch -D "worktree-$n" 2>/dev/null
    done
    git merge --abort 2>/dev/null
    git checkout -f main && git reset --hard origin/main
    git clean -fd && git worktree prune

    白話:這段=把 repo 恢復成剛 clone 完的乾淨狀態。它會丟掉未 commit 的改動與未追蹤檔(含你自己貼進.claude/ 的檔案),但不會動被 gitignore 的.todo.json(只影響 list 顯示的項目,想清就 rm -f .todo.json)與.claude/settings.local.json(存的是你核准過的權限,留著沒關係)。origin/main 是 GitHub 上那份 main 在你電腦裡的複本,所以reset --hard origin/main=回到跟 GitHub 一模一樣。最後的 git worktree prune 是第 5 章才用得到的清理,現在跑了沒作用也無害。

  2. 信任資料夾:

    第一次在這個目錄開 claude,接受信任對話後 /exit。已經做過就跳過。--worktree 要求主 checkout 先接受過信任;接受這一次,進 worktree 不會再問。

    Terminal window
    claude
  3. session A:開 worktree a。本章總共會用到三個終端機視窗(A、B 各跑一個 claude,C 做收斂),都 cd 到 repo 根目錄,可以先開好。

    開一個終端機,在 repo 根目錄:

    Terminal window
    claude --worktree a

    開好後整段貼這個提示詞:

    只改 src/todo.py 與 tests/test_todo.py:list 加 --json 輸出(印出 JSON 陣列,每筆含 text 與 done)並補測試,不要動 README,做完只 commit 不要 push
  4. session B:再開 worktree b。

    再開一個終端機,cd 到同一個 repo:

    Terminal window
    claude --worktree b

    開好後整段貼這個提示詞:

    只改 README.md 加三個使用範例,不要動 src/ 與 tests/,做完只 commit 不要 push
  5. 收斂(主 checkout):等 A、B 都回報「已 commit」再動手。A、B 在 git commit 前可能停在「要不要允許」,兩個視窗都看一眼,別乾等。

    開第三個終端機,在 repo 根目錄(不是 .claude/worktrees/ 底下):

    Terminal window
    git merge --no-ff --no-edit worktree-a && git merge --no-ff --no-edit worktree-b && python3 -m unittest -v

    &&=前一個成功才跑下一個:第一個 merge 衝突時後面不會跑,直接看故障排除「merge 衝突」。

  6. 收掉兩個 worktree:

    回終端機 A、B 各打 /exit。它會問 keep/remove,選 remove——成果已經 merge 進 main,刪掉是安全的。沒出現提問的話,自己收:git worktree remove .claude/worktrees/a && git branch -d worktree-a(b 同理)。

  7. 在主 checkout 確認乾淨:

    Terminal window
    git worktree prune && git worktree list && git branch

    選 remove 時 Claude Code 已經連分支一起刪了,不用再 git branch -d。如果剛才選的是 keep,目錄與分支都還在,要自己收:git worktree remove .claude/worktrees/a && git branch -d worktree-a(b 同理)。

❯ git merge --no-ff --no-edit worktree-a && git merge --no-ff --no-edit worktree-b && python3 -m unittest -v
Merge made by the 'ort' strategy.
……
Ran 32 tests in 3.412s
OK (skipped=2)
❯ git log --oneline --graph
*   f3a2c19 Merge branch 'worktree-b'
|\
| * 9d1e042 docs: README 加三個使用範例
* |   8c47b31 Merge branch 'worktree-a'
……
示意圖

兩個 merge 節點+32 個測試 OK=這一章的完成證明。hash 與測試秒數每次不同。

  • 步驟 5 的 merge 沒有衝突;python3 -m unittest -v 最後幾行通常是 Ran 32 tests(或更多——main 本來有 31 個,A 每補一個測試就加一)與 OK (skipped=2)。那兩個跳過的是跟教材站原始碼比對用的測試:你只 clone 了練習 repo、沒有站台原始碼,跳過是正常結果,不是錯誤(機器沒裝 node 的話會再多跳兩個,skipped=4 也正常)。
  • git log --oneline --graph 最上面有兩個 merge 節點(CLI 路線是 Merge branch 'worktree-a'、Merge branch 'worktree-b';Orca 路線分支名預設是任務名,會是 Merge branch 'a'、Merge branch 'b'——結構相同,只有名字不同)。
  • 先 python3 src/todo.py add 測試 再 python3 src/todo.py list --json,印出含 text 與 done 的 JSON 陣列(空清單印什麼依 A 的實作,不一定是 [])。
  • 步驟 7 之後 git worktree list 只剩主 checkout 一行,git branch 沒有 worktree-a、worktree-b(Orca 路線:沒有 a、b)。
  • merge 時畫面突然變成一堆 #、打字沒反應:git 開了 vim 要你確認合併訊息(指令少了 --no-edit)。打 :wq 再 Enter 就好。
  • merge 衝突:git merge --abort,回那個 session 說「只改指定檔案,重做一次」,它多半是動到了不該動的檔。
  • remove 顯示 locked:git worktree unlock .claude/worktrees/<name> 再 remove 一次。
  • B 動了 src/:提示詞邊界沒寫清楚,這是本章要你學到的事——把「不要動 src/ 與 tests/」寫進去再來一次。
  • list --json 印出用法說明、不是 JSON:A 加了函式,但沒把 --json 接進 main() 的分派(原本是 command == "list" and not rest)。回 session A 說「list --json 現在會印用法,請把旗標接進 main 的分派並補測試,做完只 commit」,主 checkout 再 git merge --no-ff --no-edit worktree-a 一次。
  • A 或 B 的 session 中途關掉、還沒 commit:再打一次 claude --worktree a(或 b)會直接回到同一個 worktree,跟它說「繼續,做完只 commit 不要 push」。
  • merge 做到一半失敗:git merge --abort 回到 merge 前的狀態;兩個 worktree 與分支都還在,修好再做一次步驟 5。
  • 想整章重做:步驟 1 的「已 clone/重跑」那一段就是為此準備的,它只清 a、b 與 main 上的練習結果。
  • Orca:不確定收斂指令該在哪裡跑:收斂(步驟 5)與清理(步驟 1、7)都在主 checkout 做,最單純的做法是開一個系統終端機到你 clone 的目錄執行。
  • Orca:merge 說找不到分支 a:Orca 建的分支名以它實際取的為準,先在主 checkout git branch 看一眼,用看到的名字 merge。

自檢(勾選會存在你的瀏覽器)0/4

以 Claude Code 2.1.246(2026-08-26)為準