격리된 워크트리 PR 흐름 (Isolated Worktree PR Flow)
메인 체크아웃을 건드리면 안 되는 상황(더티(dirty), 원격 저장소보다 뒤쳐짐, 다른 에이전트와 공유됨)에서 origin의 기본 브랜치에서 잘라낸 임시 Git 워크트리에서 변경 사항을 풀 리퀘스트(PR)로 제출하는 절차입니다. 이 흐름은 워크트리 생성, 새 워크트리 의존성 격차 해소, 유한 폴링 CI 대기, 그리고 병합 모드를 인식한 정리 가드(cleanup guards)를 포함합니다.
워크트리 격리가 필요한 이유
여러 에이전트 세션이 동일한 작업 디렉토리를 사용하면(예: 배치 세션 실행), Git의 HEAD, 현재 브랜치, 그리고 커밋되지 않은 변경 사항은 공유 상태가 됩니다. 각 세션이 서로의 작업 기반을 흔들게 됩니다. 실제 측정된 사례로, 한 세션이 git switch -c로 브랜치를 생성한 지 1분 이내에, 같은 체크아웃에서 작업하던 두 번째 세션이 해당 브랜치에서 분기하여 커밋하고 기본 브랜치에 병합한 경우가 있었습니다.
충돌 감지 패턴(몇 분 내에 충돌 확인 가능):
- 증상: 작성하지 않은 파일이 나타나거나,
git status가 세션 시작 시의 스냅샷과 일치하지 않음. - 타임스탬프 조사:
ls --time-style=full-iso(또는 플랫폼에 맞는 명령어) 실행. 여러 파일이 동일한 밀리초를 공유한다면, 수동 또는 LLM 순차 편집이 아닌 Git 작업(checkout/merge/stash)이 파일을 작성한 것임. git reflog --date=iso로 타임라인 확인: 누가, 언제 체크아웃, 커밋, 또는 병합했는지 파악.- 에이전트 환경이 세션 목록 기능을 제공한다면, 동일한 작업 디렉토리를 사용하는 다른 실행 중인 세션을 찾습니다.
충돌이 확인된 후 양보 규칙:
- 먼저 커밋한 쪽이 작성자(writer)로 남습니다. 늦게 온 쪽은 쓰기를 중단합니다. 계속 커밋하거나 브랜치를 전환하면 다른 세션의 HEAD를 변경하여 두 세션 모두 손상시킵니다.
- 늦게 온 쪽은 검증자(verifier)로서 가치를 더합니다: 작성자의 출력에 대한 적대적 검토(문서 주장 대 코드, 테스트 재실행, 게시 전 점검)와 파일을 수정하지 않는 채팅 전용 결과물을 제공합니다.
- 자신이 생성한 중간 참조(예: 빈 브랜치)만 정리하고, 다른 세션이 사용한 참조는 절대 건드리지 않습니다.
- 추가 전용 로그는 두 세션이 별도의 섹션 제목 아래에 안전하게 작성할 수 있습니다.
예방: 하나의 저장소 = 하나의 쓰기 세션. 여러 에이전트가 동일한 저장소에서 작업해야 하는 경우, 각각을 자체 워크트리에 격리하십시오. 이 스킬이 바로 그 작업을 수행합니다. 가장 저렴한 감지 습관은 작업을 시작한 직후 git status를 세션 시작 스냅샷과 다시 비교하는 것입니다.
이식성 (Portability)
이 흐름은 Windows(PowerShell 및 Git Bash)에서 검증되었지만, git 및 gh 명령어 자체는 크로스 플랫폼입니다. 셸이 다른 경우 PowerShell과 POSIX 예제를 모두 제공합니다. CI에는 Windows 호스트 외에도 Ubuntu 24.04 및 기본 macOS 15에서 합성 정리 가드 및 비공개 마커 스캐너에 대한 PowerShell 7 작업이 포함됩니다. 세 작업 모두 PR #7 실행 30216166105에서 통과했습니다. Windows 특정 부분(디렉토리 접합 및 제거 의미)에는 POSIX 심볼릭 링크 해당 항목이 인라인으로 표시됩니다.
사용 시기
- 메인 체크아웃이 더티(dirty, 커밋되지 않은 WIP), 원격 저장소보다 뒤쳐져 있거나 다른 에이전트와 공유되어, 해당 체크아웃에 직접 브랜치를 생성하면 WIP가 손상될 수 있는 경우.
- 사소한 위생 수정의 경우에도: 기존 체크아웃이 많은 커밋만큼 뒤쳐져 있고 더티 WIP를 가지고 있다면, 격리된 워크트리가 더 안전한 경로입니다(현장 테스트 완료).
- 작업별 브랜치 워크플로를 가정합니다(기본 브랜치에 직접 커밋하지 않음). 이 스킬은 브랜치가 위치하는 위치(더티 체크아웃 외부의 임시 워크트리)에 대한 메커니즘만 다룹니다.
절차
-
비파괴적으로 상태 확인. 모든
git명령어는-C <repo>와 함께 실행하고, 모든gh명령어는--repo <owner>/<name>와 함께 실행합니다. 이렇게 하면 현재 디렉토리에 의존하지 않습니다. 에이전트 환경은 호출 간에 작업 디렉토리를 재설정할 수 있으며,--repo없이 저장소 외부에서 호출하면gh가 "not a git repository" 오류를 발생시킵니다.git -C <repo> status --short # 사전 작업 WIP 기록 (끝에서 변경되지 않았는지 확인) git -C <repo> branch --show-current git -C <repo> fetch origin gh repo view <owner>/<name> --json defaultBranchRef -q .defaultBranchRef.name # 결과를 아래 <default>로 사용동일한 명령어가 POSIX 셸에서도 변경 없이 작동합니다.
-
더티 체크아웃을 건드리지 않고
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/디렉토리 또는 환경의 지정된 임시 디렉토리를 사용하고, 무거운 작업을 실행하기 전에 프로브 파일 하나를 쓰고 삭제하여 쓰기 액세스를 확인하는 것이 좋습니다. -
diff를 최소화합니다. 워크트리 내에서 작업에 필요한 파일만 편집(또는 복사)합니다. 메인 체크아웃의 추적되지 않은 WIP를 전체적으로 가져오지 마십시오. 업스트림이나 다른 에이전트가 동일한 이름의 파일을 추가할 수 있으며, 추가 파일은 병합 시 add/add 충돌을 일으키거나 PR diff를 오염시킵니다. 기본 체크아웃에서 커밋되지 않은 종속성 파일의 복사본을 포함해야 하는 경우, PR 본문에 병합 시 어느 쪽이 승리하는지 명시하십시오(현장 교훈: 이렇게 명시적으로 수행하면 잘못된 자동 해결을 방지할 수 있음).
-
새 워크트리 의존성 격차를 해결합니다. 새 워크트리에는
node_modules및 virtualenv가 없습니다(현장 테스트 완료).-
Node: 워크트리에서
npm ci를 실행합니다(lockfile 필요). 가벼운 확인만 필요한 경우, 메인 체크아웃의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>.pyPOSIX:
PYTHONPATH=<worktree>/src <repo>/.venv/bin/python -m pytest <worktree>/tests/<target_test>.py
-
-
확인이 통과하면: 커밋, 푸시, PR 생성. 커밋 메시지를 파일에 쓰고
git commit -F <msgfile>로 전달합니다. 인라인 여러 줄 메시지는 셸 따옴표 처리에 취약하며, 특히 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> ... -
유한 폴링으로 브랜치 보호 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 기간에 따라 다릅니다(일반적으로 미검증 – 저장소별로 조정).
-
병합 – 그리고 먼저 병합 모드를 결정합니다. 정리 전 병합되지 않은 작업 확인(안전 조건 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,mergeCommitgh pr merge주변의 알려진 동작(현장 테스트 완료):- 로컬 기본 브랜치가 다른 워크트리에서 체크아웃된 경우, 저장소 내부에서 실행된
gh pr merge --delete-branch는 원격 병합이 성공했더라도 로컬 후처리 중에 실패할 수 있습니다. 저장소 외부에서--repo와 함께 병합을 실행하면 로컬 후처리를 완전히 건너뛰어 사전에 오류 클래스를 방지합니다. - 그래도 오류가 보고되면 먼저
gh pr view --json state를 확인하십시오. 원격 측은 종종 이미MERGED상태입니다. 그렇다면 아래 안전 조건 후에 나머지 로컬 단계(브랜치 삭제, 워크트리 제거)를 수동으로 완료하십시오. - 저장소 내부에서 실행된
gh pr merge는 현재 워크트리를 기본 브랜치로 빠르게 전달(fast-forward)할 수 있습니다. 출력에서 이전 PR의 대규모 업데이트를 보는 것은 이상 현상이 아닙니다.
안전 조건
파괴적 단계(워크트리 제거, 브랜치 삭제, prune) 전에 실행 시점에 다음 모든 사항을 확인하십시오. 삭제 전에 확인을 실행하고, 절대 후에 실행하지 마십시오. 사전 삭제 확인을 건너뛰고 "나중에 확인"하면 가드가 무력화됩니다. 병합되지 않은 작업 실수는 브랜치가 이미 사라진 후에야 표면화됩니다(현장 교훈). 공유 체크아웃에서는 단계가 승인된 시점과 실행 시점 사이에 상태가 변할 수 있습니다. 시간이 지났다면, 실행 직전에 확인을 다시 실행하십시오(현장 테스트 완료).
-
gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAt가MERGED를 보고합니다. -
병합 모드 일치 병합되지 않은 작업 확인. 모드를 혼합하지 마십시오. 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 셸에서는echo $?확인).-d로 브랜치를 삭제하면 Git 자체가 병합되지 않은 항목이 있는 경우 여전히 거부합니다(현장 테스트 완료). 주의 사항:-d는 설정된 경우 브랜치의 업스트림에 대해 "병합됨"을 판단하고, 그렇지 않으면 HEAD를 기준으로 판단합니다. 따라서 원격 브랜치가 삭제되고 정리(prune)된 후, 체크아웃의 HEAD가 이전의 관련 없는 브랜치에 있는 경우, PR이 올바르게 병합되었음에도-d가not fully merged로 거부할 수 있습니다(현장 테스트 완료). 이 경우--is-ancestor확인이 0으로 종료된 직후에만-D로 삭제하는 것이 허용됩니다. 이는 2b와 동일한guard-then--D`` 형태입니다. 가드 없이-D를 사용하는 것은 계속 금지됩니다. - 2b —
--squash/--rebase모드:state,mergeCommit,headRefOid를gh pr view <pr-number> --repo <owner>/<name> --json 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_*변수를 스냅샷, 지우고 복원하여 저장소/추적 경로가 fixture를 벗어날 수 없도록 합니다. OS 인식 경로 비교 및 재분석 지점(reparse-point) 거부로 생성된 직접 temp 자식으로 재귀적 정리를 제한합니다. 라이브 GitHub squash 경로는 2026-07-23에 PR #2에서 측정되었습니다:MERGED, 적용된mergeCommit,headRefOid와 일치하는 변경되지 않은 로컬 및 원격 팁, 보호된 로컬-D, 명시적 원격 삭제가 모두 통과했습니다. 라이브 GitHub rebase 경로는 2026-07-26에 PR #5에서 측정되었습니다:MERGED, 다시 작성된 적용된mergeCommit, 기본 브랜치 조상 계보 밖에 있는 원래 HEAD,headRefOid와 일치하는 변경되지 않은 로컬 및 원격 팁, 보호된 로컬-D, 명시적 원격 삭제 및 소유 워크트리 정리까지 모두 통과했습니다.
- 2a —
-
워크트리 제거:
git -C <repo> worktree remove <worktree-path> git -C <repo> worktree prunePOSIX:
git -C <repo> worktree remove <worktree-path> git -C <repo> worktree prune -
잔여물 정리:
scripts/cleanup-private-markers.ps1은 CI 및 로컬 실행에서 "private"으로 표시된 파일을 정리합니다. GitHub Actions는 스크립트를 체크아웃하지만 실제 저장소 파일을 건드리지 않도록 설계된 합성git status를 생성합니다. 대조적으로, 로컬 실행은 워크트리 또는 체크아웃 내부에서 실행되며, 마커(워크트리에서 생성된 파일, CI 전용 아티팩트)를 제거합니다. 자세한 내용은 스크립트 설명서를 참조하십시오. -
_worktrees/디렉토리를 정리합니다. (워크트리 제거 후 일부 파일이 남을 수 있음, 필드 교훈.)# 워크트리 제거 후 남은 파일 수동 정리 Remove-Item -Recurse -Force <worktree-path>POSIX:
# 워크트리 제거 후 남은 파일 수동 정리 rm -rf <worktree-path>정리 단계가 끝나면,
git worktree prune을 다시 실행하십시오.
스크립트
-
scripts/test-cleanup-guards.ps1— 가드 2a 및 2b에 대한 일회용 합성 Git 기록을 생성하고 검증합니다. 모든 가드 확인 경로, 승인 시나리오 및 거부 시나리오를 테스트합니다. Azure Pipelines 및 GitHub Actions에서 모두 CI로 실행됩니다. PowerShell 7이 필요합니다 (pwsh).Set-StrictMode -Version Latest,$ErrorActionPreference = 'Stop'으로 실행합니다. 시스템 및 전역 Git 구성을 격리하고 주변GIT_*변수를 지웁니다. 일회용git init작업 디렉토리에서 실행되며, 아무것도 남기지 않습니다. 실제 파일 시스템에 대한 의도하지 않은 영향을 방지하기 위해 생성된 모든 경로는 소스 트리 외부에 있는 일회용 임시 디렉토리에 속하는 것으로 확인됩니다. 이 테스트의 존재 이유: Git 2.45에는 워크트리 내에서git worktree remove가 실패하는 회귀(regression)가 있습니다. (fatal: ... is a but principal, and we will not harm it). 이 테스트는 사용자가 겪기 전에 그러한 회귀를 감지합니다. -
scripts/cleanup-private-markers.ps1— CI 실행 및 로컬 개발 작업 디렉토리에서 비공개 마커(워크트리에서 생성된 파일, CI 전용 아티팩트)를 정리합니다. 자세한 내용은 설명서를 참조하십시오. 마커 경로는scripts/private-marker-paths.json에 정의되어 있습니다. -
scripts/private-marker-paths.json—scripts/cleanup-private-markers.ps1에서 사용하는 경로 패턴 목록입니다. 이 패턴은 저장소의 다른 파일보다 우선하여 제거되어야 하는 워크트리 생성 파일 및 CI 전용 아티팩트를 지정합니다.
환경 변수
없음. 이 스킬은 환경 변수에 의존하지 않습니다. Git 및 gh CLI가 설치되어 있고 PATH에서 사용 가능하다고 가정합니다.
관련 기술
git worktreegh prgit merge-base --is-ancestor- PowerShell 및 Bash 스크립팅
결론
격리된 워크트리 PR 흐름은 여러 AI 에이전트가 동일한 Git 저장소에서 안전하게 작업할 수 있는 방법을 제공합니다. 충돌 감지, 격리된 워크트리 사용, 병합 모드 인식 정리 가드를 통해 공유 환경에서도 각 에이전트의 작업 무결성을 보장합니다.