Bạn spawn một agent để refactor một module trong khi main session đang chạy thứ khác. Agent edit thẳng vào working tree hiện tại. Vài phút sau, bạn tiếp tục edit ở main session và nhận ra những file bạn đang dựa vào đã bị agent sửa nửa chừng, commit dở, hoặc xóa mất. Hoặc ngược lại: bạn edit một file, agent đè lên bởi vì nó đang dựa vào snapshot lúc nó được spawn, không biết bạn đã sửa.
Đây là vấn đề của shared working tree. Giải pháp là isolation: "worktree" khi spawn agent. Bài này giải thích cơ chế bên dưới: CC tạo git worktree riêng như thế nào, tại sao base branch phải là parent HEAD chứ không phải origin/main, và hook WorktreeCreate can thiệp vào quy trình đó ở điểm nào.
Vấn đề: shared working tree
Khi bạn spawn agent không có isolation, agent chạy trên cùng thư mục với main session. Hai hệ quả thực tế:
Race condition trên file. Main session đang đọc src/api/users.ts, agent đồng thời edit file đó để refactor. File bị sửa giữa chừng, main session đọc phiên bản không nhất quán. Với tốc độ agentic loop, lỗi này xảy ra trước khi bạn kịp notice.
Stale view. Agent được spawn lúc T=0 với snapshot trạng thái thư mục lúc đó. Main session tiếp tục thay đổi file. Agent cứ dựa vào snapshot T=0, không biết trạng thái hiện tại. Kết quả là agent commit code với assumption sai.
Vấn đề trầm trọng hơn khi main session đang ở trạng thái không clean: đang tail log, đang giữ .env đã sửa tạm thời, đang debug một vấn đề production với scratch file nằm khắp nơi. Switch branch hoặc để agent can thiệp lúc này làm hỏng toàn bộ debug state.
Giải pháp: isolation: “worktree”
Khi spawn agent với isolation: "worktree", CC tạo một git worktree riêng biệt cho agent đó. Agent làm việc trong thư mục worktree của riêng nó, trên branch riêng của nó. Main session tiếp tục ở thư mục và branch của mình. Hai bên không can thiệp nhau.
Main session Worktree agent
----------- --------------
cwd: /project cwd: /project/.claude/worktrees/<slug>
branch: feature/foo branch: worktree/<slug>
working tree: clean working tree: isolated copy
Agent có thể commit tùy ý trong worktree của nó mà không ảnh hưởng gì đến main session. Main session không thấy uncommitted changes của agent. Không có race condition.
Cấu trúc worktree
Git worktree là tính năng native của git: cho phép checkout nhiều branch cùng lúc vào các thư mục khác nhau, tất cả chia sẻ cùng một .git object store. Không phải clone, không duplicate object. Chỉ là một working directory thêm trỏ vào repo.
Khi CC tạo worktree cho agent:
- Thư mục:
<git-root>/.claude/worktrees/<slug>/(thư mục này gitignore bởi nested.claude/) - Branch:
worktree/<slug>với flag--no-track(không có upstream tracking mặc định) - Base: HEAD của parent session lúc spawn (không phải
origin/<default-branch>)
Branch worktree/<slug> là branch thực, bạn có thể checkout, compare, open PR từ đó. Sau khi agent xong việc, nó push branch này lên remote và mở PR vào target branch.
Tại sao base phải là HEAD, không phải origin/main
Đây là chi tiết quan trọng nhất của isolation.
Giả sử bạn đang làm việc trên branch feature/checkout-flow. Bạn có 5 commit chưa merge vào main. Bạn spawn một worktree agent để viết unit test cho flow đó.
Nếu agent base worktree trên origin/main: agent không thấy 5 commit của bạn. Nó viết test cho code version cũ. Test sai từ gốc, hoặc không compile vì thiếu interface mới bạn vừa thêm.
Nếu agent base trên HEAD của parent session: agent thấy đúng trạng thái code bạn đang làm việc. Test viết đúng, compile được, coverage đúng phần đang phát triển.
HEAD (feature/checkout-flow) <- worktree base từ đây
|
| 5 commits chưa merge
|
origin/main <- base sai nếu dùng binary default
Binary mặc định của CC base worktree trên origin/<default-branch>. Với hầu hết workflow có feature branch, điều này sai. Hook WorktreeCreate tồn tại để sửa lỗi này.
Hook WorktreeCreate
Hook WorktreeCreate là shell script chạy thay cho hành vi default của CC khi tạo worktree. Bạn đăng ký nó trong ~/.claude/settings.json:
{
"hooks": {
"WorktreeCreate": "~/.claude/hooks/worktree-create.sh"
}
}
Script nhận JSON qua stdin với hai trường: .name (slug của agent) và .cwd (working directory của parent session). Script output path của worktree vừa tạo lên stdout. CC dùng path đó để chạy agent.
Logic của hook (rút gọn từ script thực):
# Đọc input
NAME=$(printf '%s' "$INPUT" | jq -r '.name')
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd')
# Tính path worktree và tên branch
WT_DIR="${GIT_TOPLEVEL}/.claude/worktrees/${NAME}"
BRANCH="worktree/${NAME}"
# Tạo worktree, base = HEAD (không phải origin/main)
git worktree add --no-track -B "$BRANCH" "$WT_DIR" HEAD
# Output path để CC biết chạy agent ở đâu
printf '%s\n' "$WT_DIR"
Ba điểm khác binary default:
- Path: đặt worktree vào
.claude/worktrees/<slug>/thay vì/tmp/hoặc chỗ binary chọn - Base branch:
HEADthay vìorigin/<default-branch> - No-track: branch mới không có upstream tracking, push đầu tiên phải
git push -u origin worktree/<slug>
Hook còn có logic idempotent: nếu worktree đã tồn tại (agent được re-spawn), nó return path hiện có thay vì tạo lại.
Native flag thay thế hook (CC 2.1.133+)
Từ CC v2.1.133, có native setting trong settings.json:
{
"worktreeBaseRef": "head"
}
Giá trị "head" làm binary tự base trên parent HEAD, không cần hook. Giá trị mặc định là "fresh" (base trên origin/<default-branch>).
Tuy nhiên, khi hook WorktreeCreate có mặt, binary nhường hoàn toàn cho hook: worktreeBaseRef bị bypass. Hook phù hợp cho trường hợp cần logic phức tạp hơn, ví dụ worktree path tùy chỉnh, naming convention riêng, hoặc validation thêm. Nếu không có nhu cầu đó, worktreeBaseRef: "head" trong settings là đủ và đơn giản hơn.
Workflow đầy đủ với worktree agent
1. Main session đang làm việc trên feature/foo (có uncommitted edits OK)
2. Spawn agent: isolation: "worktree", brief với target branch = "feature/foo"
3. Hook chạy: tạo .claude/worktrees/<slug>/, base HEAD, branch worktree/<slug>
4. Agent làm việc trong worktree riêng: read, edit, commit, push
5. Agent mở PR: worktree/<slug> -> feature/foo
6. Agent báo xong, PR URL trả về
7. Main session: git fetch + gh pr merge, sau đó git pull --ff-only
8. Main session không cần checkout sang branch agent, không mất scratch state
Điểm quan trọng ở bước 7: main session không git checkout sang branch agent. Để merge, dùng gh pr merge hoặc git fetch origin && git merge --ff-only origin/worktree/<slug> ngay từ branch hiện tại. Checkout là antipattern vì nó reset working tree, xóa scratch state, phá vỡ monitoring loop nếu đang có.
Khi nào dùng worktree isolation
Dùng isolation: "worktree" khi bất kỳ điều sau đây đúng:
| Tình huống | Lý do cần worktree |
|---|---|
| Main session đang mid-debug, tail log, giữ scratch file | Shared tree sẽ hỏng debug state |
| Task cần PR/review/CI trước khi merge | Worktree branch là unit tự nhiên cho PR |
| Nhiều hotfix song song không liên quan nhau | Mỗi hotfix một worktree, không conflict |
| Agent cần edit branch khác với main session | Worktree checkout branch khác tự nhiên |
Không cần worktree khi task là 1-2 file nhỏ, main session không giữ scratch state, và không cần review gate. Với case đó, spawn agent bình thường (không có isolation) là đủ và nhanh hơn.
Antipattern cần tránh
Switch branch trong main session để lấy code agent. Ví dụ: agent push worktree/<slug>, bạn làm git checkout worktree/<slug> trong main session để xem, rồi manual merge. Điều này:
- Reset working tree: uncommitted edits của main session biến mất
- Nếu đang tailing log hay giữ
.envtạm: tất cả mất theo - Dễ merge nhầm, không có PR review trail
Làm đúng: mở PR, merge qua gh pr merge, git pull --ff-only để cập nhật branch hiện tại. Main session không cần rời khỏi branch của mình.
Spawn nhiều no-isolation agent trên cùng file scope. Không có worktree, hai agent cùng edit src/api/ là last-writer-wins. Race condition, không rollback được. Nếu cần nhiều agent song song trên overlapping files, mỗi agent phải có worktree riêng với file scope tách biệt rõ ràng.
Tóm tắt và bài tiếp theo
isolation: "worktree"tạo git worktree riêng cho agent, tránh race condition và stale view với main session.- Worktree branch
worktree/<slug>base trên HEAD của parent session, không phảiorigin/main, để agent thấy đúng trạng thái code đang phát triển. - Hook
WorktreeCreateoverride hành vi default của binary: custom path, base branch, no-track. Từ CC 2.1.133+,worktreeBaseRef: "head"trong settings là đủ cho use case đơn giản. - Merge kết quả bằng PR, không checkout sang branch agent trong main session.
Bài 21 sẽ đi vào workflow cụ thể: dùng worktree agent để xử lý hotfix khi main session đang monitoring production, từng bước từ spawn đến merge mà không làm gián đoạn debug state.
Liên quan:
- Bài 27 (Background session và worktree) đi sâu vào setting
worktree.bgIsolationcho lead session, một feature riêng tách rờiisolation: "worktree"cho subagent đề cập ở bài này. Đọc bài 27 nếu bạn muốn hiểu rõ khi nào CC force bg session vào worktree, khi nào không, và cách opt-out cho repo cần edit working copy trực tiếp. - Skill
nf-agentsDecision 2 giải thích chi tiếtWorktreeCreatehook (kèm incident từng làm mất 8 commit in-progress), cách hook override default base branch của binary.
Bài thuộc series Claude Code từ zero. Series plan tại bài giới thiệu.