10 분 소요

📦 병렬 Claude 세션 작업 안전하게 합치기 4부작 — 같은 프로젝트에서 Claude Code 세션을 여러 개 병렬로 굴릴 때 코드가 서로 덮어써지는 문제를, worktree 로 세션마다 폴더·브랜치를 격리하고(1편), 그 브랜치를 PR·CI·리뷰 관문에 태워 통합하고(2편), 여러 PR 의 충돌을 통합 담당 세션이 자동으로 감지·정리하고(3편), 끝으로 git 을 전혀 몰라도 Claude 에게 이 전 과정을 통째로 맡기는 법(4편)까지 이어집니다. 전체 4편.
  1. 여러 Claude 세션이 서로 덮어쓸 때 — git worktree 로 격리하기지금 글
  2. worktree 브랜치를 PR 로 묶고 자동 리뷰까지 통합하기
  3. 여러 PR 의 충돌을 자동으로 감지하고 정리하기 — 통합 오케스트레이션
  4. git 을 몰라도 괜찮다 — Claude 에게 병렬 작업과 통합을 통째로 맡기기

Summary

Claude Code 를 여러 개 띄워놓고 병렬로 굴리기 시작하면 누구나 한 번쯤 겪는 사고가 있어요. 같은 폴더에서 세션 A, B, C 를 각각 다른 이름으로 띄워 각자 작업을 시키고, 나중에 git 으로 합치려는데 어떤 세션이 방금 만든 코드가 다른 세션 커밋에 통째로 덮어써져서 사라지는 일이죠. 저도 딱 이걸 겪고 나서야 “아, 한 폴더에서 여러 에이전트를 굴리는 건 애초에 구조가 틀렸구나” 하고 정리하게 됐어요.

결론부터 말하면, 문제의 뿌리는 git merge 를 잘못 써서가 아니라 여러 세션이 물리적으로 같은 워킹트리(working tree) 하나를 공유한다는 데 있습니다. 이 글에서는 왜 그런 일이 벌어지는지, 그리고 git worktree 로 세션마다 독립된 폴더·브랜치를 주고 한 곳에서만 순차적으로 합치는 워크플로우를 정리해볼게요.

💡 이 글에서 다루는 것

  • 같은 폴더 동시 작업이 왜 코드를 유실시키는지 (lost update 의 구조)
  • “그냥 git merge 하면 되잖아” 가 왜 이 경우엔 안 통하는지
  • git worktree 로 세션마다 폴더+브랜치를 격리하는 법
  • 각 세션 작업을 한 곳에서 순차적으로 안전하게 합치는 통합 워크플로우
  • worktree 를 못 쓰는 상황에서의 차선책(파일 영역 분리·자주 커밋)
  • 자주 부딪히는 함정과 최종 체크리스트



1. 무슨 일이 벌어졌나

상황을 재현해볼게요. 프로젝트 폴더 하나(~/Projects/myapp)에서 터미널 탭을 세 개 열고, 각각 Claude Code 를 띄웁니다. 세션 이름만 다를 뿐 작업 디렉토리는 셋 다 똑같아요.

탭1: cd ~/Projects/myapp && claude   # 세션 "auth"  — 로그인 기능
탭2: cd ~/Projects/myapp && claude   # 세션 "api"   — API 엔드포인트
탭3: cd ~/Projects/myapp && claude   # 세션 "docs"  — 문서 정리

셋 다 열심히 파일을 고칩니다. 그리고 여기서 사고가 나요. 대표적인 두 갈래가 있습니다.

(1) 저장이 겹치는 경우. 세션 authconfig.py 를 읽어서 A 버전으로 고쳐 저장합니다. 거의 동시에 세션 api 도 같은 config.py 를 (아직 A 수정이 반영 안 된 상태로) 읽어놨다가 B 버전으로 저장해요. 디스크에는 마지막에 쓴 B 만 남고, A 의 수정은 흔적도 없이 날아갑니다. git 이 개입할 틈도 없어요. 그냥 파일시스템 레벨에서 덮어쓴 거예요.

(2) 커밋·머지가 겹치는 경우. 세션마다 각자 git add . && git commit 을 합니다. 문제는 셋 다 같은 브랜치(main 이든 gh-pages 든) 위에 서 있다는 거예요. api 세션이 커밋할 때 워킹트리에는 auth 세션이 아직 커밋 안 한 변경분이 섞여 있거나, 반대로 누군가 git checkout . / git reset --hard 로 워킹트리를 되돌리면서 다른 세션의 미커밋 작업을 쓸어버립니다. 여기에 git push -f 까지 끼면 원격의 히스토리마저 서로 덮어써요.

핵심은 이거예요. 세 세션이 브랜치는 “논리적으로 다른 일” 을 한다고 생각하지만, 물리적으로는 파일 하나·인덱스 하나·HEAD 하나를 공유하고 있다는 것. git 은 이걸 세 갈래로 인식하지 못합니다. 그냥 한 사람이 정신없이 왔다갔다 편집하는 걸로 볼 뿐이에요.

⚠️ 이건 Claude Code 만의 문제가 아니라, 한 워킹트리에서 여러 주체가 동시에 편집하면 무조건 생기는 고전적인 lost update 입니다. 사람 둘이 같은 폴더를 SSH 로 붙어 동시에 고쳐도 똑같이 나요. 에이전트는 손이 빨라서 더 자주 부딪힐 뿐이에요.



2. “그냥 git merge 하면 되잖아” 가 안 되는 이유

여기서 많이들 “브랜치 나눠서 merge 하면 git 이 알아서 합쳐주잖아” 라고 생각해요. 맞는 말인데, 전제가 하나 빠졌습니다. git 의 3-way merge 가 작동하려면 합치려는 두 변경이 각자 독립된 커밋 히스토리로 존재해야 해요. 그런데 한 폴더에서 세션을 돌리면 그 전제가 깨집니다.

git 워킹트리의 상태는 크게 세 층이에요.

정체 한 폴더에서 세션 3개면
워킹 디렉토리 실제 파일들 1개를 공유 — 마지막 저장이 이김
인덱스(staging) git add 한 스냅샷 1개를 공유 — 서로의 add 가 섞임
HEAD / 현재 브랜치 지금 체크아웃된 지점 1개를 공유 — 브랜치 전환도 전역

세 세션이 브랜치를 각자 만들어 git checkout -b feature-auth 하는 것조차 위험해요. 브랜치 체크아웃은 폴더 전체의 전역 상태라서, auth 세션이 브랜치를 바꾸는 순간 api 세션이 보고 있던 파일들도 그 브랜치 기준으로 싹 바뀝니다. 한 세션이 밟고 선 바닥을 다른 세션이 갈아끼우는 셈이에요.

즉 merge 가 나빠서가 아니라, 애초에 merge 에 넣을 깨끗한 두 갈래를 만들지 못하는 구조가 문제입니다. 두 변경이 같은 워킹트리에서 서로를 밟고 지나간 뒤라, git 손에 들어올 때는 이미 한쪽이 사라진 상태예요. 합칠 재료 자체가 유실된 거죠.

💡 정리하면 필요한 건 “더 똑똑한 merge” 가 아니라 세션마다 독립된 워킹트리·인덱스·HEAD 입니다. 그래야 git 이 진짜로 세 갈래를 인식하고, 나중에 3-way merge 로 제대로 합쳐줘요.



3. 해결의 핵심 — git worktree

브랜치마다 폴더를 따로 git clone 하는 방법도 있지만, 그러면 저장소 히스토리가 통째로 복제돼서 무겁고 원격 설정도 따로 놀아요. 그래서 정답은 git worktree 입니다.

git worktree하나의 .git 저장소(오브젝트·히스토리)를 공유하면서, 워킹 디렉토리와 인덱스·HEAD 만 여러 벌로 늘려주는 기능이에요. 딱 우리가 원하던 그림이죠.

  • 히스토리·오브젝트는 한 벌만 → 디스크 절약, git fetch 한 번이면 모든 worktree 가 최신 커밋 공유
  • 워킹 디렉토리·인덱스·HEAD 는 worktree 마다 독립 → 세션끼리 파일이 안 섞임
  • 같은 브랜치를 두 worktree 에 동시에 체크아웃하는 걸 git 이 막아줌 → 실수로 겹치는 걸 원천 차단

그림으로 보면 이렇게 바뀝니다.

Before ── 한 폴더, 세 세션이 공유 (충돌)
  ~/Projects/myapp   ← 세션 auth·api·docs 가 전부 여기

After ── worktree 로 세션마다 폴더·브랜치 분리
  ~/Projects/myapp          (main, 통합 담당)
  ~/Projects/myapp-auth     (feature-auth)   ← 세션 auth
  ~/Projects/myapp-api      (feature-api)    ← 세션 api
  ~/Projects/myapp-docs     (feature-docs)   ← 세션 docs

이제 세션 auth 가 브랜치를 바꾸든 파일을 고치든, 그건 myapp-auth 폴더 안에서만 일어나요. api 세션은 자기 폴더에서 태평하게 자기 일만 합니다. 물리적으로 격리됐기 때문에 서로 덮어쓸 방법이 없어요.

💡 Claude Code 자체에도 worktree 격리를 돕는 기능들이 있어요(작업을 별도 worktree 에서 실행하는 옵션 등). 다만 원리를 알아야 어떤 상황에 뭘 쓸지 판단이 서니까, 이 글은 순수 git worktree 명령 기준으로 풀어갈게요.



4. 실전 셋업 — 세션마다 worktree 하나씩

메인 저장소 폴더(~/Projects/myapp)에서 시작합니다. 이 폴더는 앞으로 통합(merge) 전용으로만 쓸 거예요. 여기서 각 작업용 worktree 를 만듭니다.

cd ~/Projects/myapp

# 형식: git worktree add <새 폴더 경로> -b <새 브랜치명>
git worktree add ../myapp-auth -b feature-auth
git worktree add ../myapp-api  -b feature-api
git worktree add ../myapp-docs -b feature-docs

-b feature-auth 는 “새 브랜치를 만들면서 그 브랜치를 이 폴더에 체크아웃하라” 는 뜻이에요. 이미 있는 브랜치를 붙이고 싶으면 -b 를 빼고 브랜치명만 적으면 됩니다.

# 이미 존재하는 브랜치를 worktree 로 꺼내기
git worktree add ../myapp-hotfix hotfix-login

현재 worktree 목록은 이렇게 확인해요.

git worktree list

정상이면 이런 출력이 떠요. 각 폴더가 어느 브랜치를 물고 있는지 한눈에 보입니다.

/home/me/Projects/myapp        a1b2c3d [main]
/home/me/Projects/myapp-auth   a1b2c3d [feature-auth]
/home/me/Projects/myapp-api    a1b2c3d [feature-api]
/home/me/Projects/myapp-docs   a1b2c3d [feature-docs]

이제 Claude Code 를 각 worktree 폴더 안에서 띄웁니다. 이게 핵심이에요. 같은 폴더에서 세 번 띄우는 대신, 폴더를 갈라서 하나씩.

# 탭1
cd ~/Projects/myapp-auth && claude
# 탭2
cd ~/Projects/myapp-api  && claude
# 탭3
cd ~/Projects/myapp-docs && claude

각 세션 입장에서는 그냥 평범한 git 저장소 하나가 통째로 자기 것이에요. 다른 세션의 존재를 몰라도 되고, 알 필요도 없습니다. 자기 폴더에서 마음껏 고치고 git add·git commit 하면 그게 자기 브랜치에 차곡차곡 쌓여요.

사실 이 “worktree addcdclaude” 3단계는 Claude Code 의 --worktree 플래그로 한 줄로 줄일 수 있어요. 세션을 띄울 때 워크트리를 알아서 만들어 그 안에서 시작합니다.

# git worktree add + cd + claude 를 한 번에
claude --worktree auth      # .claude/worktrees/auth/ 에 worktree-auth 브랜치로 시작
claude --worktree api       # 다른 탭에서
claude --worktree docs

--worktree (짧게 -w) 는 워크트리를 .claude/worktrees/<이름>/ 에 만들고 worktree-<이름> 브랜치를 붙여줘요(기본 브랜치에서 분기). --tmux 를 같이 주면 tmux 패널로도 띄워집니다. 다만 세션마다 이 플래그를 붙여줘야 하고, “여러 세션을 자동으로 각자 워크트리에” 몰아주는 전역 설정은 없어요(그건 데스크톱 앱이 세션마다 자동으로 해줍니다). 그래도 손으로 git worktree add 를 치는 것보다 훨씬 간편하죠.

여기서 꼭 짚을 게 하나 있어요. 세션에 이름을 다르게 주는 --name(짧게 -n) 은 격리가 아니에요. --name 은 프롬프트 박스·/resume 피커·터미널 제목에 뜨는 표시용 이름표일 뿐, 작업 폴더와 파일은 그대로 공유합니다. 그래서 세션 이름만 갈라놓고 같은 폴더에서 돌리면 1장에서 본 덮어쓰기가 그대로 재현돼요. 이름이 다른 것과 작업 공간이 다른 것은 완전히 별개입니다.

claude --name auth          # 이름표만 다름 → 여전히 같은 폴더 공유 → 덮어쓰기 O
claude --worktree auth      # 폴더 자체가 갈림 → 진짜 격리 → 덮어쓰기 X

🚨 이게 “세션명은 다 다르게 했는데 왜 코드가 덮어써지지?” 의 정확한 원인이에요. --name 은 라벨, --worktree 는 격리. 병렬로 돌릴 거면 이름이 아니라 워크트리(또는 폴더)를 갈라야 합니다. 둘을 같이 써서 claude --worktree auth --name auth 처럼 이름표까지 붙이면 보기도 편해요.

✅ 팁: worktree 폴더 이름(또는 --worktree <이름> 의 이름)에 작업 성격을 박아두면 터미널 탭이 여러 개일 때 “지금 내가 어느 세션인지” 헷갈리지 않아요. 프롬프트에 git 브랜치를 표시하는 셸 설정도 같이 켜두면 더 안전합니다.



5. 각 세션 작업을 한 곳에서 합치기

이제 세션들이 각자 커밋을 쌓았어요. 합칠 차례입니다. 여기서 규칙이 딱 하나 있어요.

🚨 통합(merge)은 메인 폴더 한 곳에서, 한 번에 하나씩, 순차적으로만 한다.

작업용 worktree 안에서 다른 브랜치를 merge 하려 들면 다시 여러 세션이 통합 브랜치를 동시에 건드리는 원점으로 돌아가요. 통합은 반드시 통합 전용 폴더(~/Projects/myapp, main)에서만.

cd ~/Projects/myapp
git checkout main

# 각 브랜치를 순서대로 하나씩 병합
git merge feature-auth
git merge feature-api
git merge feature-docs

서로 다른 파일을 건드렸다면 git 이 알아서 3-way merge 로 조용히 합쳐줍니다. 1장에서 유실됐던 A 수정이 이번엔 안 사라지는 이유가 바로 이거예요. 각 변경이 독립된 커밋으로 살아있으니 git 이 양쪽을 다 반영합니다.

같은 파일의 같은 줄을 두 세션이 고쳤다면 그때는 정직하게 충돌(conflict)이 납니다. 이건 사고가 아니라 git 이 “여기 둘이 겹쳤으니 사람이 판단해라” 하고 정상적으로 알려주는 거예요. 조용히 한쪽을 날리는 것보다 백 배 낫습니다.

# 충돌 파일 확인
git status

# 파일 열어서 <<<<<<< / ======= / >>>>>>> 마커 기준으로 정리한 뒤
git add <해결한 파일>
git merge --continue

충돌 해결 자체를 또 다른 Claude 세션한테 맡길 수도 있어요. 다만 그것도 통합 폴더에서 도는 세션 하나한테만 시키세요. 여러 세션이 동시에 충돌을 풀면 그게 또 새로운 충돌의 씨앗이 됩니다.

통합이 끝났으면 원격에 올립니다. 이때 git push 만, 절대 git push -f 는 쓰지 마세요. force push 는 방금 순서대로 쌓은 통합 히스토리를 남의 커밋째로 날려버릴 수 있어요.

git push origin main



6. worktree 를 다 쓰고 정리하기

작업이 끝난 worktree 는 남겨두면 헷갈리니 깔끔하게 치웁니다. 폴더를 rm -rf 로 그냥 지우지 말고 git worktree remove 를 쓰세요. git 이 내부 메타데이터까지 같이 정리해줘요.

cd ~/Projects/myapp

# 병합 끝난 worktree 제거
git worktree remove ../myapp-auth
git worktree remove ../myapp-api
git worktree remove ../myapp-docs

# 합쳐진 브랜치도 정리
git branch -d feature-auth feature-api feature-docs

폴더를 손으로 지워버려서 목록에 유령이 남았다면 이걸로 청소해요.

git worktree prune

⚠️ 커밋 안 한 변경이 남은 worktree 는 git worktree remove 가 거부합니다(안전장치). 진짜 버릴 거면 --force 를 붙이되, 정말 날려도 되는지 한 번 더 확인하고요.



7. worktree 를 못 쓰는 상황이라면 (차선책)

가끔 worktree 로 못 가는 사정이 있어요. 빌드 산출물이 폴더에 종속적이거나, .env·거대한 node_modules 를 폴더마다 복제하기 부담스럽거나. 그럴 땐 한 폴더를 유지하되 충돌 확률을 최대한 낮추는 쪽으로 갑니다. 완벽한 격리는 아니지만 사고를 크게 줄여줘요.

  • 파일 영역을 아예 안 겹치게 나눈다. 세션 A 는 src/auth/ 만, 세션 B 는 src/api/ 만. 같은 파일을 두 세션이 만질 일을 애초에 없앱니다. 겹치는 공통 파일(config, 라우터 등록부 등)은 한 세션이 전담.
  • 작게, 자주 커밋한다. 한 세션이 한 덩어리 작업을 끝내면 바로 커밋. 워킹트리에 미커밋 변경이 오래 떠 있을수록 다른 세션이 덮어쓸 창이 넓어져요.
  • 작업 전 git pull/git status 로 현재 상태를 먼저 확인하고 시작. 남이 방금 바꾼 걸 모르고 옛날 버전 위에 얹으면 그게 lost update 예요.
  • git checkout . / git reset --hard / git stash 를 함부로 쓰지 않는다. 이 셋은 워킹트리를 통째로 되돌려서 다른 세션의 미커밋 작업을 소리 없이 지웁니다. 여러 세션이 도는 폴더에서는 특히 조심.
  • git push -f 금지. 원격 히스토리를 덮어써서 남의 커밋을 날리는 가장 흔한 사고 경로예요.

그래도 이건 어디까지나 임시방편입니다. 병렬 세션을 상시로 굴릴 거면 결국 worktree 로 가는 게 맞아요.



8. 자주 부딪히는 함정

worktree 를 쓰기 시작하면 새로 만나는 자잘한 벽들이 있어요. 미리 알아두면 안 당합니다.

증상 원인 대응
fatal: '...'
already checked out
같은 브랜치를 두 worktree 에
체크아웃 시도
브랜치는 worktree 당 하나.
새 브랜치를 -b 로 파거나
기존 걸 옮기기
로컬 서버 포트 충돌 여러 worktree 에서 같은 포트로
동시에 serve 실행
포트를 다르게
(-P 4001, --port 3001 등)
node_modules·.env 가 없음 worktree 는 추적 파일만 복제,
untracked 는 안 따라옴
worktree 마다 npm install /
.env 복사 한 번씩
빌드 산출물이 섞임 _site/·dist/ 를 커밋에 포함 .gitignore 에 넣어 추적 제외
메인 폴더에서 브랜치 삭제 거부 그 브랜치가 다른 worktree 에서
체크아웃 중
먼저 해당 worktree 를 remove 후 삭제

특히 이 블로그(dorumugs.github.io)처럼 Jekyll 로컬 프리뷰를 돌리는 저장소라면, worktree 두 곳에서 bundle exec jekyll serve 를 동시에 켜면 4000 포트가 겹쳐요. 한쪽은 --port 4001 로 띄우면 됩니다.



9. 최종 체크리스트

병렬 세션을 안전하게 굴리기 위한 최소 규칙을 정리하면 이래요.

  • 세션마다 별도 worktree 폴더 + 별도 브랜치 를 준다 (git worktree add ../x -b feature-x)
  • Claude Code 는 각 worktree 폴더 안에서 띄운다 (같은 폴더 다중 실행 금지)
  • 통합은 메인 폴더 한 곳에서, 한 번에 하나씩 순차 merge
  • 충돌은 사고가 아니라 정상 신호 — 조용히 한쪽 날리지 말고 손으로 해결
  • git push -f, 무분별한 git reset --hard/checkout . 금지
  • 끝난 worktree 는 git worktree remove 로 정리
  • worktree 를 못 쓰면 최소한 파일 영역 분리 + 작게 자주 커밋

핵심을 한 줄로 줄이면, “세션의 논리적 분리(브랜치)를 물리적 분리(폴더)로 뒷받침해라” 예요. git worktree 가 그 다리를 놔줍니다. 한 폴더에서 여러 에이전트를 굴리며 코드가 사라지던 문제가, 폴더를 가르는 순간 거짓말처럼 없어질 거예요.



일단 오늘은 여기까지…..
다음 글에서는 이렇게 나눈 worktree 브랜치들을 자동으로 PR 로 묶어 리뷰까지 태우는 흐름을 정리해볼게요.


다음 글 →: (2/4) worktree 브랜치를 PR 로 묶고 자동 리뷰까지 통합하기