Community寫作與編輯github.com

h8nc4y/isolated-worktree-pr-flow

用於 Claude Code 和 Codex 的代理人技能:當主要簽出工作目錄處於髒亂、落後或與其他代理人共用狀態時,從遠端預設分支建立一個臨時 git 工作樹來建立 PR,並附帶合併模式感知的清理防護。

isolated-worktree-pr-flow 是什麼?

此技能專為 AI 開發代理人(如 Claude Code 和 Codex)設計,旨在解決多個代理人共用同一個 git 倉庫時產生的衝突問題。其核心機制是從遠端預設分支建立一個隔離的臨時 git 工作樹,讓每個代理人在自己的隔離環境中獨立工作,避免因共用 HEAD、分支或未提交變更而互相干擾。技能詳細描述了完整的操作流程,包括安全檢查、工作樹建立、處理依賴缺口、有界輪詢 CI 狀態,以及根據合併模式(merge/squash/rebase)進行清理的防護條件。此外,它還說明了如何透過監控檔案修改時間戳記和 `git reflog` 來檢測衝突。此技能已在 Windows、macOS 和 Ubuntu 上經過測試,並包含跨 shell 的指令碼範例。

相容平台✓Claude Code✓Codex CLI~Cursor
npx skills add h8nc4y/isolated-worktree-pr-flow

Installed? Explore more 寫作與編輯 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

隔離工作樹 PR 流程

當主要簽出不可被觸碰時——因為它處於髒亂(未提交的進行中工作)、落後於遠端,或與其他代理人共用——從 origin 的預設分支建立一個臨時 git 工作樹並以 PR 形式交付變更的程式。此流程涵蓋工作樹建立、全新工作樹的依賴缺口、有界 CI 輪詢,以及合併模式感知的清理防護。

為何需要工作樹隔離

當多個代理人工作階段進入同一個工作目錄時(例如透過批次工作階段啟動),git 的 HEAD、目前分支和未提交的變更會成為共用狀態——每個工作階段都會在其他工作階段之下移動基礎。在一個被量測的事件中,一個工作階段用 git switch -c 建立分支後不到一分鐘,第二個工作階段在同一個簽出中便從該分支分岔,提交並合併到預設分支。

檢測模式(可在數分鐘內確認衝突):

  1. 跡象:出現你未撰寫的檔案,或 git status 不再符合工作階段開始時的快照。
  2. 時間戳記審查:ls --time-style=full-iso(或你平台的等效指令)。數個檔案共用相同的毫秒數表示 git 操作(簽出 / 合併 / stash)寫入了它們,而非手動或 LLM 順序編輯。
  3. git reflog --date=iso 可釐清時間線:誰簽出、提交或合併,以及何時發生。
  4. 如果你的代理人環境提供工作階段列表功能,請用它來尋找具有相同工作目錄的其他執行中工作階段。

一旦確認衝突後的讓步規則:

  • 先提交的一方保持為寫入者。後來者停止寫入:繼續提交或切換分支會移動另一個工作階段的 HEAD 並破壞雙方。
  • 後來者改為作為驗證者增加價值:對撰寫者輸出進行對抗性審查(文件聲明 vs 程式碼,重新執行測試,發佈前檢查),以及不碰觸任何檔案的純聊天交付物。
  • 僅清理你自己建立的中間引用(例如一個空分支);永遠不要碰觸其他工作階段使用過的引用。
  • 僅追加日誌對於兩個工作階段在各自的章節標題下寫入是安全的。

預防措施:一個儲存庫 = 一個寫入工作階段。當多個代理人必須在同一個儲存庫上工作時,將每個代理人隔離在自己的工作樹中——這正是此技能所做的。最便宜的檢測習慣是在開始工作後立即重新比對 git status 與你的工作階段開始快照。

可攜性

此流程已在 Windows(PowerShell 加上 Git Bash)上經過實戰測試,但 git 和 gh 命令本身是跨平台的。在 shell 不同的地方,同時給出了 PowerShell 和 POSIX 範例。CI 包含在 Ubuntu 24.04 和原生 macOS 15 上執行的 PowerShell 7 工作,用於綜合清理防護和私有標記掃描器,以及 Windows 主機。這三個工作在 PR #7 執行編號 30216166105 中全部通過。Windows 特定的部分——目錄連結及其移除語義——在內文中有 POSIX 符號連結的等效說明。

何時使用

  • 主要簽出處於髒亂(未提交的進行中工作)、落後於遠端,或與其他代理人共用,且直接在該簽出中建立分支可能會破壞該進行中工作。
  • 即使是一個小的整潔修復:如果現有的簽出落後許多提交並帶有髒亂的進行中工作,隔離工作樹是更安全的路線(經實戰測試)。
  • 假設採用每個任務一個分支的工作流程(絕不直接提交到預設分支)。此技能僅涵蓋該分支所在位置的機制:一個在髒亂簽出之外的臨時工作樹。

程序

  1. 檢查狀態,非破壞性地。每個 git 命令都使用 -C <repo> 執行,每個 gh 命令都使用 --repo <owner>/<name> 執行,這樣就不依賴於當前目錄——代理人環境可能會在呼叫之間重設工作目錄,而沒有 --repo,gh 在儲存庫外部被呼叫時會因為「不是一個 git 儲存庫」而失敗。

    git -C <repo> status --short          # 記錄工作前狀態(最後驗證未更改)
    git -C <repo> branch --show-current
    git -C <repo> fetch origin
    gh repo view <owner>/<name> --json defaultBranchRef -q .defaultBranchRef.name   # 將結果用作下面的 <default>
    

    相同的命令在 POSIX shell 中也可以原樣運作。

  2. 從 origin 的預設分支建立一個臨時工作樹,不觸碰髒亂的簽出。用步驟 1 的結果替換 <default>——不要硬編碼 main;預設為 master 的儲存庫仍然很常見。

    git -C <repo> worktree add -b fix/<task> ..\_worktrees\<task> origin/<default>
    

    POSIX:

    git -C <repo> worktree add -b fix/<task> ../_worktrees/<task> origin/<default>
    

    將工作樹放在確認可寫入的位置。系統範圍的臨時目錄有時無法從沙箱化的代理人環境中寫入(經實戰測試)。偏好將 _worktrees/ 目錄放在儲存庫旁邊或你環境指定的臨時目錄中,並在執行任何繁重操作之前,寫入一個探測檔案並刪除它以確認寫入權限。

  3. 保持差異最小。只編輯(或複製)任務所需且在工作樹內的檔案。不要將主要簽出的未追蹤進行中工作大規模帶入——上游或其他代理人可能會新增同名檔案,而額外的檔案會在合併時導致新增/新增衝突,或污染 PR 差異。如果你必須從基礎簽出包含一個未提交的依賴檔案的副本,請在 PR 主體中說明合併時哪一方獲勝(實戰教訓:明確執行此操作避免了錯誤的自動解析)。

  4. 處理全新工作樹的依賴缺口。一個新的工作樹沒有 node_modules 和 virtualenv(經實戰測試)。

    • Node:在工作樹中執行 npm ci(需要鎖定檔案)。僅用於輕量驗證時,臨時連結到主要簽出的 node_modules 也可以——但在清理之前移除該連結(安全條件 5)。PowerShell 目錄連結:

      New-Item -ItemType Junction -Path <worktree>\node_modules -Target <repo>\node_modules
      # 移除時必須僅刪除連結,絕不刪除目標:使用 rmdir。
      cmd /c rmdir "<worktree>\node_modules"
      

      POSIX 符號連結:

      ln -s <repo>/node_modules <worktree>/node_modules
      # 用普通的 rm 移除連結本身(不帶 -r,不帶尾隨斜線)。
      rm <worktree>/node_modules
      
    • Python:重複使用主要儲存庫的 virtualenv 直譯器並將其指向工作樹的原始碼,僅執行目標測試。PowerShell:

      $env:PYTHONPATH = "<worktree>\src"
      & <repo>\.venv\Scripts\python.exe -m pytest <worktree>\tests\<target_test>.py
      

      POSIX:

      PYTHONPATH=<worktree>/src <repo>/.venv/bin/python -m pytest <worktree>/tests/<target_test>.py
      
  5. 當驗證通過時:提交、推送並建立 PR。將提交訊息寫入一個檔案並用 git commit -F <msgfile> 傳遞——內聯多行訊息在 shell 引用下很脆弱,尤其是在 Windows 上。

    git -C <worktree> add <target files only>
    git -C <worktree> commit -F <msgfile>
    git -C <worktree> push -u origin fix/<task>
    gh pr create --repo <owner>/<name> --head fix/<task> ...
    
  6. 使用有界輪詢等待分支保護 CI。gh pr checks --watch 是一個無界等待——代理人框架和審批層可能會拒絕它或掛起(經實戰測試)。使用一次性檢查搭配嘗試次數上限:

    gh pr checks <pr-number> --repo <owner>/<name> --watch=false
    gh pr view <pr-number> --repo <owner>/<name> --json state,statusCheckRollup
    

    有界迴圈,POSIX。如果框架阻塞了前景 sleep(Claude Code 會),則將迴圈作為背景任務執行,或跳過迴圈並將一次性檢查交織在其他工作片段之間:

    for i in $(seq 1 10); do gh pr checks <pr-number> --repo <owner>/<name> --watch=false && break; sleep 30; done
    

    有界迴圈,PowerShell:

    for ($i = 1; $i -le 10; $i++) {
      gh pr checks <pr-number> --repo <owner>/<name> --watch=false
      if ($LASTEXITCODE -eq 0) { break }
      Start-Sleep -Seconds 30
    }
    

    正確的上限和間隔取決於儲存庫的 CI 持續時間(一般情況下未驗證——請根據每個儲存庫調整)。

  7. 合併——並先決定合併模式,因為它決定了清理前未合併工作的驗證(安全條件 2):

    • --merge(合併提交):分支提交會成為預設分支的祖先,因此用 merge-base --is-ancestor 驗證,並用 branch -d 刪除(安全條件 2a;經實戰測試)。
    • --squash / --rebase:分支提交永遠不會成為預設分支的祖先,因此對分支執行 --is-ancestor 會永遠回傳 1,即使在完全成功的合併之後也是如此。請改用「MERGED + headRefOid 匹配」防護(安全條件 2b)。
    gh pr merge <pr-number> --repo <owner>/<name> --merge      # squash 策略的儲存庫:--squash,然後使用防護 2b
    gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAt,mergeCommit
    

    關於 gh pr merge 的已知行為(經實戰測試):

    • 如果本地預設分支在另一個工作樹中被簽出,從儲存庫內部執行的 gh pr merge --delete-branch 可能會在其本地後續處理中失敗,即使遠端合併成功。從儲存庫外部使用 --repo 執行合併會完全跳過本地後續處理,從根本上避免了此類失敗。
    • 如果無論如何回報了失敗,請先檢查 gh pr view --json state——遠端端通常已經是 MERGED。如果是這樣,請在滿足以下安全條件後手動完成其餘的本地步驟(分支刪除、工作樹移除)。
    • 從儲存庫內部執行的 gh pr merge 可能會將目前工作樹快轉到預設分支;在其輸出中看到來自早期 PR 的大量更新並非異常。

安全條件

在任何破壞性步驟之前(工作樹移除、分支刪除、修剪),請在執行時檢查以下所有項目——在刪除之前執行驗證,絕不能在之後執行。跳過刪除前檢查並「事後驗證」會使防護失效:未合併工作的錯誤只有在分支已經消失時才會顯現(實戰教訓)。在共用的簽出中,狀態可能會在步驟被批准和執行之間漂移;如果時間已經過去,請在執行前立即重新執行檢查(經實戰測試)。

  1. gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAt 報告 MERGED。

  2. 符合合併模式的未合併工作驗證。不要混合使用模式:在 squash 之後,防護 2a 對於一個正確合併的 PR 也永遠不會通過;而 squash 之後的 branch -d 要麼會透過合併到上游的邏輯溜過去(如果 origin/fix/... 仍然存在——這不是祖先保證),要麼會被永遠拒絕為「未完全合併」(如果上游已經消失)。

    • 2a —— --merge(合併提交)模式: git -C <repo> merge-base --is-ancestor fix/<task> origin/<default> 回傳 0(在 PowerShell 中檢查 $LASTEXITCODE,在 POSIX shell 中檢查 echo $?)。用 -d 刪除分支,這樣 git 本身仍然會拒絕任何未合併的內容(經實戰測試)。注意:-d 是根據分支的上游(如果設定)來判斷「已合併」,否則根據 HEAD 來判斷——因此在遠端分支被刪除和修剪後,並且簽出中的 HEAD 停留在一個更舊、不相關的分支上時,-d 可能會拒絕並顯示 not fully merged,即使 PR 已正確合併(經實戰測試)。在這種情況下,僅在 --is-ancestor 檢查回傳 0 之後立即使用 -D 刪除是可以接受的——與 2b 相同的「先防護再 -D」形式;未經防護的 -D 仍然被禁止。
    • 2b —— --squash / --rebase 模式:透過 gh pr view <pr-number> --repo <owner>/<name> --json state,mergeCommit,headRefOid 獲取 state、mergeCommit 和 headRefOid。請注意,mergeCommit 是一個 JSON 物件——提交雜湊是其 oid 欄位(-q .mergeCommit.oid 可提取它);headRefOid 是一個純字串。然後確認兩者:(i) git -C <repo> merge-base --is-ancestor <mergeCommit-oid> origin/<default> 回傳 0,且 (ii) git -C <repo> rev-parse fix/<task> 等於 headRefOid。檢查 (ii) 是 squash 模式下「沒有遺留提交」的替代方案:它證明已合併的 PR 頭部恰好是你的本地分支頂端。僅當兩者都成立時,使用明確的 git branch -D 刪除(此防護取代了 -d)。 防護 2b 的拓撲結構和拒絕路徑由 scripts/test-cleanup-guards.ps1 中的一次性綜合 Git 歷史覆蓋。該測試隔離了系統/全域 Git 配置、鉤子、簽署和 rebase.updateRefs;快照、清除並恢復每個環境中的 GIT_* 變數,以便儲存庫/追蹤路徑無法逃離固定裝置;並透過作業系統感知的路徑比較和重新解析點拒絕,將其遞迴清理限制在其產生的直接 temp 子目錄。即時 GitHub squash 路徑已於 2026-07-23 使用 PR #2 量測:MERGED、已落地的 mergeCommit、匹配 headRefOid 的未更改本地和遠端頂端、受防護的本地 -D 以及明確的遠端刪除均通過。即時 GitHub rebase 路徑已於 2026-07-26 使用 PR #5 量測:MERGED、一個重寫的已落地 mergeCommit、在預設分支祖先鏈之外的原始 HEAD、匹配 headRefOid 的未更改本地和遠端頂端、受防護的本地 -D、明確的遠端刪除以及擁有的工作樹清理均通過。

相關技能