(3/4) 여러 PR 의 충돌을 자동으로 감지하고 정리하기 — 통합 오케스트레이션
- 여러 Claude 세션이 서로 덮어쓸 때 — git worktree 로 격리하기
- worktree 브랜치를 PR 로 묶고 자동 리뷰까지 통합하기
- 여러 PR 의 충돌을 자동으로 감지하고 정리하기 — 통합 오케스트레이션 ← 지금 글
- git 을 몰라도 괜찮다 — Claude 에게 병렬 작업과 통합을 통째로 맡기기
Summary
2편에서 각 세션의 worktree 브랜치를 PR 로 올리고 CI·리뷰 관문을 붙였어요. PR 이 하나씩 초록불이 되면 순서대로 합치면 됐죠. 그런데 세션이 늘고 작업이 겹치기 시작하면 새로운 문제가 나옵니다. PR 들이 서로 충돌하는 거예요. main 하고는 안 부딪히는데 PR #42 와 #43 이 같은 파일을 건드려서, 하나를 합치는 순간 다른 하나가 빨간불로 바뀝니다.
이걸 사람이 매번 “지금 뭐가 충돌났지? 어느 걸 먼저 합쳐야 하지?” 하고 챙기면 병렬 작업의 이점이 사라져요. 그래서 이번 편은 통합만 전담하는 세션(integrator) 하나를 두고, 그 세션이 충돌을 스스로 감지하고 안전한 순서로 정리해서 합치는 오케스트레이션 패턴을 정리해볼게요.
💡 이 글에서 다루는 것
- “통합 담당(integrator) 세션” 이라는 역할 분리
gh로 PR 의 충돌 여부(mergeable)를 감지하는 법- 여러 충돌 PR 을 어떤 순서로 정리할지 정하기
- 충돌 해결을 세션에게 맡길 때 git 이 아니라 Claude 가 유리한 이유
- “머지 가능한 것부터 → 나머지 rebase → 반복” 오케스트레이션 루프
- 폭주를 막는 가드레일과 체크리스트
1. “통합 담당 세션” 이라는 역할
2편 끝에서 규칙 하나를 세웠어요. “각 세션은 자기 PR·자기 브랜치만 건드린다. 합치는 결정은 통합 담당 한 명만.” 이번 편은 그 “통합 담당” 을 아예 독립된 세션 하나로 만들어 자동화하는 이야기예요.
구도를 그려보면 이렇게 나뉩니다.
| 역할 | 위치 | 하는 일 |
|---|---|---|
| 작업 세션 (N개) | 각자의 worktree 폴더 | 자기 브랜치에서 기능 구현 + 자기 PR 관리 |
| 통합 세션 (1개) | 메인 폴더(main) |
PR 상태 감지 · 충돌 정리 · 순서대로 머지 |
통합 세션은 코드를 새로 짜지 않아요. 오직 “지금 열린 PR 들 중 뭐가 합칠 수 있고, 뭐가 충돌났고, 어떤 순서로 정리할지” 만 관리합니다. 작업 세션들이 요리사라면 통합 세션은 완성된 접시를 순서대로 내보내는 패스(pass) 담당인 셈이에요.
🚨 통합 세션은 메인 폴더 한 곳에서 하나만 돌려요. 통합 세션이 둘이면 둘이 같은
main을 동시에 건드려서, 1편에서 봤던 그 충돌이 통합 단계에서 재현됩니다.
2. 충돌 감지 — PR 이 합칠 수 있는 상태인가
사람은 PR 화면을 눈으로 보고 “Conflicts” 빨간 배지를 확인하죠. 통합 세션은 그걸 gh 로 조회합니다. 핵심 필드는 mergeable 과 mergeStateStatus 예요.
gh pr list --state open \
--json number,title,headRefName,mergeable,mergeStateStatus
출력은 이런 JSON 이에요(보기 좋게 정리하면).
#42 API 엔드포인트 추가 feature-api MERGEABLE CLEAN
#43 응답 스키마 정리 feature-schema CONFLICTING DIRTY
#44 문서 정리 feature-docs MERGEABLE BEHIND
세 상태의 의미만 알면 돼요.
| 필드/값 | 의미 | 통합 세션의 판단 |
|---|---|---|
mergeable: MERGEABLE |
충돌 없음 | 합칠 후보 |
mergeable: CONFLICTING |
main 또는 다른 변경과 충돌 |
먼저 정리 필요 |
mergeable: UNKNOWN |
GitHub 이 아직 계산 중 | 잠깐 기다렸다 재조회 |
mergeStateStatus: BEHIND |
base 가 앞서감(뒤처짐) | rebase 로 최신화 |
mergeStateStatus: CLEAN |
지금 바로 머지 가능 | 우선 머지 대상 |
jq 로 “충돌난 PR 만” 뽑는 것도 한 줄이에요.
gh pr list --state open --json number,headRefName,mergeable \
| jq -r '.[] | select(.mergeable=="CONFLICTING") | "\(.number) \(.headRefName)"'
이제 통합 세션은 “무엇이 합칠 수 있고 무엇이 막혔는지” 를 매 순간 정확히 압니다. 눈이 아니라 데이터로요.
⚠️ 방금 push 한 직후엔
mergeable이UNKNOWN으로 잠깐 뜹니다. GitHub 이 백그라운드로 머지 가능성을 계산하는 중이에요.UNKNOWN이면 몇 초 뒤 다시 조회하게 만드세요.
3. 정리 순서 정하기
충돌난 PR 이 여러 개면 아무거나 먼저 손대면 안 돼요. 순서가 중요합니다. 통합 세션이 따르는 원칙은 단순해요.
- 합칠 수 있는 것(
MERGEABLE·CLEAN)부터 머지한다. 막힌 걸 붙들고 있지 말고, 지금 되는 걸 먼저main에 넣어 진도를 뺍니다. - 하나 합칠 때마다
main이 바뀌므로, 나머지를 다시 평가한다. 방금 머지로 인해 새로 충돌이 생기거나, 반대로 풀리기도 해요. - 충돌난 것은 base(
main) 기준으로 rebase 해서 정리한다. rebase 하면 그 PR 이 최신main위에서 다시 계산되고, 진짜 충돌만 남습니다. - 작고 독립적인 PR 을 먼저. 큰 PR 을 먼저 넣으면 나머지가 전부 그 위에서 재정렬돼야 해서 충돌 면적이 커져요. 작은 것부터 흘려보내는 게 전체 충돌을 줄입니다.
말로 풀면 “되는 것부터 내보내고, 하나 내보낼 때마다 나머지를 다시 보고, 막힌 건 최신 기준으로 재정렬한다” 예요. 이게 다음 절의 루프가 됩니다.
4. 충돌 해결 — 여기서 Claude 가 유리하다
git rebase 를 돌리다 충돌이 나면, 결국 누군가 “이 두 변경을 어떻게 합치는 게 맞는지” 를 판단해야 해요. 기계적으로 한쪽을 고르는 -X ours/-X theirs 같은 전략도 있지만, 그건 의미를 무시하고 한쪽을 버리는 거라 위험합니다.
바로 이 지점이 통합 세션에 Claude 를 쓰는 이유예요. 충돌 마커(<<<<<<< / ======= / >>>>>>>)가 낀 파일을 열어서, 양쪽이 무엇을 하려던 코드인지 읽고 둘 다 살리는 방향으로 합치는 판단은 단순 스크립트가 못 해요. 사람이 하던 그 판단을 세션이 대신하는 겁니다.
통합 세션이 충돌난 PR 하나를 정리하는 흐름은 이래요. 그 PR 의 브랜치를 worktree 로 꺼내(2편의 격리 원칙 유지) 최신 main 위로 rebase 하고, 충돌을 해결한 뒤 다시 올립니다.
# 통합 세션이 충돌 PR(feature-schema)을 정리
git fetch origin
git worktree add ../myapp-fix feature-schema
cd ../myapp-fix
git rebase origin/main
# ← 여기서 충돌 발생. 세션이 파일을 열어 양쪽 의도를 읽고 해결
git add -A
git rebase --continue
git push --force-with-lease
cd -
git worktree remove ../myapp-fix
핵심은 충돌 해결을 별도 worktree 에서 한다는 거예요. 통합 세션이 서 있는 메인 폴더(main)의 상태를 건드리지 않으면서 문제 브랜치만 손봅니다. 정리가 끝나면 그 PR 의 mergeable 이 다시 MERGEABLE 로 바뀌어요.
💡 애매하면 멈추는 게 정답이에요. 충돌 해결이 “이건 둘 중 뭘 원한 건지 코드만 봐선 모르겠다” 싶으면, 세션이 임의로 고르지 말고 사람에게 물어보게 하세요. 잘못 합친 코드가 초록불로 통과하는 게 제일 무섭습니다.
5. 오케스트레이션 루프
앞의 원칙들을 하나의 반복 루프로 묶으면 통합 세션의 동작이 완성돼요.
열린 PR 이 남아있는 동안 반복:
1. gh 로 모든 열린 PR 의 mergeable 상태를 조회
2. MERGEABLE·CLEAN 이 있으면 → 그중 가장 작은 걸 머지
3. 방금 머지로 main 이 바뀌었으니, 나머지를 다시 조회
4. CONFLICTING 이면 → 그 브랜치를 rebase·충돌 해결·재푸시
5. 아무것도 진행 못 하고 CONFLICTING 만 남으면 → 사람 호출
스크립트로 골격을 잡으면 이런 모양이에요(통합 세션이 참고하는 형태).
#!/usr/bin/env bash
# integrate.sh — 열린 PR 을 머지 가능한 것부터 순서대로 통합
set -euo pipefail
while true; do
# 지금 바로 합칠 수 있는 PR 번호 하나 (작은 번호 우선)
ready=$(gh pr list --state open --json number,mergeable,mergeStateStatus \
| jq -r '[.[] | select(.mergeable=="MERGEABLE" and
(.mergeStateStatus=="CLEAN" or .mergeStateStatus=="BEHIND"))]
| sort_by(.number) | .[0].number // empty')
if [ -z "$ready" ]; then
echo "바로 합칠 PR 없음. 남은 충돌 PR 을 확인하세요."
gh pr list --state open --json number,headRefName,mergeable \
| jq -r '.[] | select(.mergeable=="CONFLICTING") | "충돌: #\(.number) \(.headRefName)"'
break
fi
echo "머지: #$ready"
gh pr merge "$ready" --squash --delete-branch
sleep 3 # GitHub 이 나머지 PR 의 mergeable 재계산할 시간
done
이 스크립트는 충돌 없는 것만 자동으로 흘려보내고, 진짜 충돌은 사람(또는 통합 세션의 판단)에게 넘깁니다. 실제로 통합 세션에게는 “이 루프를 돌리되, 4번 충돌 정리 단계는 네가 파일을 읽고 해결해라” 라고 맡기는 식이에요. 기계적으로 되는 부분은 스크립트가, 판단이 필요한 부분은 세션이 나눠 갖는 거죠.
6. 폭주를 막는 가드레일
통합을 자동화할수록 “잘못 돌면 크게 잘못 도는” 위험도 커져요. 몇 가지 안전선을 꼭 같이 둡니다.
main강제 푸시 절대 금지. 통합 세션이main히스토리를-f로 덮으면 남의 커밋이 통째로 날아가요. 브랜치 재정렬은 항상 작업 브랜치 쪽에서만--force-with-lease.- 한 번에 하나만 머지. 여러 PR 을 동시에 머지 시도하면 3절의 “머지 순서 문제” 가 재발합니다. 루프는 반드시 순차.
- 충돌 해결은 애매하면 중단. 4절대로, 판단이 안 서는 충돌은 임의로 고르지 말고 사람 호출.
- 보호 규칙은 그대로. 통합 세션도 CI·리뷰 관문을 우회하지 못하게(2편의 브랜치 보호), 통과한 PR 만 머지하게 둡니다.
- 무한 루프 방지. rebase 했는데도 계속
CONFLICTING이면 몇 번 시도 후 멈추고 보고. 같은 PR 을 무한히 rebase 하지 않게.
🚨 자동화의 목표는 “사람을 완전히 빼는” 게 아니라 “기계적인 부분만 빼고, 판단이 필요한 순간에만 사람을 부르는” 거예요. 애매할 때 멈추는 세션이 끝까지 밀어붙이는 세션보다 백 배 안전합니다.
7. 자주 부딪히는 함정
| 증상 | 원인 | 대응 |
|---|---|---|
mergeable 이 계속 UNKNOWN |
push 직후 GitHub 계산 중 | 몇 초 뒤 재조회 (루프에 sleep) |
| 머지했는데 다음 PR 이 빨간불 | base 바뀌어 새 충돌 발생 | 매 머지 후 나머지 재평가 (루프 3번) |
| rebase 가 끝없이 충돌 | 두 PR 이 근본적으로 같은 설계를 다르게 감 | 자동 해결 포기, 작성 세션끼리 조율 |
통합 세션이 main 을 rebase |
대상을 잘못 잡음 | rebase 는 작업 브랜치만, main 은 절대 |
| 충돌 해결이 조용히 한쪽 삭제 | -X ours/theirs 남용 |
의미 기반 해결, 애매하면 사람 호출 |
특히 세 번째 — rebase 가 끝없이 충돌하는 건, 사실 도구 문제가 아니라 두 세션이 같은 부분을 서로 다른 설계로 짠 신호예요. 이건 통합 단계에서 억지로 합칠 게 아니라, 1편의 “파일 영역을 애초에 겹치지 않게 나눈다” 로 거슬러 올라가 예방하는 게 맞아요.
8. 최종 체크리스트
- 통합은 메인 폴더의 통합 세션 하나가 전담 (작업 세션과 역할 분리)
- 충돌 감지는 눈이 아니라
gh ... --json mergeable,mergeStateStatus로 - 합칠 수 있는 것부터, 작은 것부터 머지하고 매번 나머지 재평가
- 충돌 정리는 별도 worktree 에서 rebase, 해결은 의미 기반으로
main강제 푸시 금지 · 재정렬은 작업 브랜치--force-with-lease- 애매한 충돌·무한 rebase 는 멈추고 사람 호출
3편까지 오면 병렬 세션의 전체 파이프라인이 완성돼요. 격리(1편) → 검증(2편) → 자동 통합(3편) 이 한 줄로 흐릅니다. 여기까지는 git·gh 명령을 꽤 많이 다뤘는데, 마지막 4편에서는 이 모든 걸 git 을 전혀 몰라도 Claude 에게 통째로 맡기는 법을 정리해볼게요.
일단 오늘은 여기까지…..
다음 글에서는 명령어를 하나도 외우지 않고, 자연어 지시만으로 이 전 과정을 돌리는 법을 풀어볼게요.
← 이전 글: (2/4) worktree 브랜치를 PR 로 묶고 자동 리뷰까지 통합하기 | 다음 글 →: (4/4) git 을 몰라도 괜찮다 — Claude 에게 통째로 맡기기