-
[AI] git worktree와 cmux 활용한 AI 병렬 개발 워크플로우Dev 2026. 7. 23. 17:58반응형
AI 코딩 에이전트를 하나만 사용할 때는 터미널 창 하나면 충분하다. 하지만 기능 구현, 리뷰 반영, 빌드 확인을 동시에 맡기기 시작하면 문제가 달라진다.
에이전트를 여러 개 실행하는 것 자체는 어렵지 않다. 진짜 어려운 일은 각 에이전트가 서로의 코드를 밟지 않도록 작업 공간을 나누고, 어느 세션이 내 판단을 기다리는지 놓치지 않는 것이다.
DDD 13기 사이드 프로젝트 Pickflow에서 iOS 개발을 진행하며 이 문제를 자주 겪었다. Xcode와 Tuist를 사용하는 프로젝트라 브랜치 전환 비용도 컸다. 결국 브랜치를 계속 바꾸는 방식을 버리고, 티켓마다 git worktree를 만들었다.
여기에 여러 터미널과 AI 에이전트 세션을 정리하는 cmux를 결합하면 역할이 명확해진다.
· git worktree: 브랜치와 파일, 빌드 상태를 디렉터리 단위로 격리한다.
· cmux: 여러 디렉터리의 터미널과 에이전트 상태를 한 화면에서 관리한다.
· Claude Code나 Codex: 격리된 작업 공간 안에서 실제 작업을 수행한다.
· 개인 스킬: worktree 생성부터 빌드 검증까지 반복 절차를 자동화한다.
이 글에서는 Pickflow에서 실제로 사용한 worktree 운영 경험을 바탕으로, cmux를 더했을 때 AI 병렬 개발 워크플로우가 어떻게 완성되는지 정리한다.
문제는 에이전트 수가 아니라 작업 디렉터리였다
기능 브랜치에서 작업하다가 리뷰 코멘트나 핫픽스가 들어오면 보통 다음 과정을 거친다.
단계 1 — 진행 중인 변경 사항을 git stash로 치운다.
단계 2 — 다른 브랜치로 checkout한다.
단계 3 — Tuist 프로젝트를 다시 생성한다.
단계 4 — SPM 의존성을 다시 확인한다.
단계 5 — Xcode 인덱싱과 빌드가 끝나기를 기다린다.
단계 6 — 작업을 마치고 원래 브랜치로 돌아와 같은 과정을 반복한다.
일반적인 프로젝트에서도 번거롭지만 Xcode와 Tuist 조합에서는 더 비싸다. 브랜치마다 프로젝트 구성이 달라질 수 있고, xcodeproj 재생성이나 인덱싱, 빌드 캐시 무효화가 이어진다. 몇 분짜리 전환이 하루에 여러 번 쌓이면 흐름이 자주 끊긴다.
AI 에이전트를 병렬로 실행하면 문제는 더 커진다. 같은 working tree에서 두 세션이 서로 다른 작업을 시작하면 파일 변경, 브랜치 이동, 생성 파일이 섞일 수 있다. 터미널 창만 여러 개 띄웠다고 병렬 작업 환경이 만들어지는 것은 아니다.
먼저 코드가 놓이는 장소부터 분리해야 했다.
git worktree: 브랜치 전환을 폴더 전환으로 바꾸다
git worktree는 하나의 Git 저장소에 여러 working tree를 연결하는 기능이다. Git 객체와 이력은 공유하지만 각 worktree는 별도의 HEAD, index, 작업 파일을 가진다. 덕분에 여러 브랜치를 서로 다른 디렉터리에 동시에 checkout할 수 있다.
Pickflow의 워크스페이스는 다음과 같이 구성했다.
· DDD-13th-Workspace/: 프로젝트 전체를 담는 최상위 폴더
· DDD-13-iOeS-iOS-develop/: develop 브랜치에 고정한 메인 클론
· DDD-13-iOeS-iOS-KAN-145/: feature/KAN-145 worktree
· DDD-13-iOeS-iOS-KAN-146/: feature/KAN-146 worktree
· DDD-13-iOeS-iOS-PV-15/: feature/PV-15 worktree
· DDD-13-iOeS-Android/: 같은 프로젝트의 Android 저장소
· Configs/: worktree에 복사할 공용 설정 (GoogleService-Info.plist, Common.xcconfig)
· 그 밖의 기획 문서, API 명세, 샘플 앱
여기서 정한 규칙은 세 가지였다.
1. 메인 클론은 develop에 고정한다
메인 클론에서는 기능을 개발하지 않는다. 항상 최신 develop을 확인하는 기준점이자, 새 worktree를 만드는 base로만 사용한다.
이 규칙 덕분에 “지금 이 폴더에 남은 변경 사항이 있나?”를 확인하지 않고도 새 작업을 시작할 수 있다.
2. 티켓 하나를 폴더 하나에 대응시킨다
티켓 번호가 KAN-146이면 폴더도 DDD-13-iOeS-iOS-KAN-146으로 만든다. 폴더명만 봐도 어떤 작업인지 알 수 있고, AI 에이전트의 실행 위치도 모호하지 않다.
3. 프로젝트 전체를 담는 상위 워크스페이스를 둔다
최상위 폴더는 Git 저장소가 아니다. iOS와 Android 저장소, worktree, 공용 설정, 문서를 함께 담는 프로젝트 컨테이너다.
Git은 저장소를 관리하지만 실제 개발에는 명세, 샘플 앱, 플랫폼별 저장소도 필요하다. 이를 한 폴더 아래에 두니 사람과 에이전트 모두 필요한 맥락을 찾기 쉬워졌다.
새 티켓을 시작할 때 실제로 필요한 일
처음에는 아래 명령을 매번 직접 실행했다.
단계 1 — 메인 클론으로 이동한다: cd DDD-13-iOeS-iOS-develop
단계 2 — develop을 최신화한다: git pull --ff-only origin develop
단계 3 — 브랜치와 worktree를 만든다: git worktree add -b feature/KAN-146 ../DDD-13-iOeS-iOS-KAN-146 develop
단계 4 — 공용 설정을 복사한다: cp -R ../Configs ../DDD-13-iOeS-iOS-KAN-146/Configs
단계 5 — 새 worktree로 이동한다: cd ../DDD-13-iOeS-iOS-KAN-146
단계 6 — 의존성을 설치한다: tuist install
단계 7 — 프로젝트를 생성한다: tuist generate --no-open
단계 8 — 시뮬레이터 대상으로 검증 빌드한다: xcodebuild -workspace Pickflow.xcworkspace -scheme Pickflow -destination 'generic/platform=iOS Simulator' build
단순해 보이지만 단계마다 프로젝트 특유의 함정이 있었다.
· Configs/는 gitignored 파일이라 worktree에 따라오지 않았다. GoogleService-Info.plist, Common.xcconfig 등이 없으면 프로젝트 생성부터 실패했다.
· tuist install이 tuist generate보다 먼저 실행되어야 했다. 새 worktree에는 외부 의존성이 준비되어 있지 않기 때문이다.
· 로컬 develop이 오래된 상태일 수 있었다. 팀원의 PR이 머지된 뒤에도 갱신하지 않으면 최근 API나 DTO 변경을 놓친 채 새 브랜치를 만들게 된다.
· 생성 성공과 빌드 성공은 다르다. 마지막에 검증 빌드까지 통과해야 실제 작업 가능한 worktree라고 볼 수 있었다.
한 단계를 빼먹으면 worktree는 만들어졌지만 앱은 빌드되지 않는 애매한 상태가 남았다. 반복 절차를 기억력에 맡길 이유가 없었다.
명령어 묶음을 개인 스킬로 만들다
그래서 worktree 준비 과정을 Claude Code 개인 스킬로 만들었다. 이제 새 티켓을 시작할 때 필요한 입력은 KAN-146 워크트리 파줘 한 줄이다.
스킬은 develop 최신화 → feature 브랜치 생성 → worktree 추가 → gitignored Configs 복사 → tuist install → tuist generate → 시뮬레이터 대상 검증 빌드 → 결과 보고 순서로 동작한다.
스킬을 설계할 때 두 가지 원칙을 넣었다.
첫째, 실패하면 멈추고 원인을 보고하되 자동으로 worktree를 지우지 않는다. 프로젝트 생성이나 빌드 실패는 설정 누락이나 의존성 드리프트를 알려주는 진짜 신호일 수 있다. 자동 롤백으로 현장을 없애면 오히려 진단이 어려워진다.
둘째, 이 자동화는 팀 저장소가 아니라 개인 스킬로 관리한다. 절대 경로와 내 로컬 환경에 묶인 도구이므로 팀 전체에 강제할 규칙이 아니기 때문이다.
문서는 절차를 설명하지만 스킬은 절차를 실행한다. “Configs를 복사하고 install을 먼저 하세요”라고 적는 것보다, 티켓 번호 하나로 재현되게 만드는 편이 누락을 줄였다.
worktree만으로는 여러 AI 세션을 보기 어렵다
worktree를 만들면 파일 충돌 문제는 해결된다. 하지만 KAN-145, KAN-146, PV-15에서 에이전트를 동시에 실행하면 다른 문제가 나타난다.
· 어떤 터미널이 어떤 폴더인지 탭 제목만으로 구분하기 어렵다.
· 한 에이전트가 질문을 기다리는 동안 다른 로그에 묻힌다.
· 빌드, 테스트, 에이전트 대화를 한 화면에 배치하기 어렵다.
· 작업 수가 늘수록 “어디까지 확인했지?”라는 운영 비용이 커진다.
여기서 cmux의 역할이 생긴다.
cmux는 Ghostty 기반의 macOS 네이티브 터미널이다. 세로형 workspace 목록, split pane, 알림 패널, CLI와 소켓 API를 제공하며 여러 AI 코딩 에이전트를 동시에 다루는 흐름을 목표로 한다.
중요한 점은 cmux 자체가 코드 격리를 만들어 주는 것은 아니라는 것이다. 같은 checkout에서 pane만 여러 개 열면 에이전트끼리 여전히 같은 파일을 수정한다. 먼저 worktree로 물리적인 경계를 만들고, 그다음 cmux workspace를 연결해야 한다.
worktree 하나를 cmux workspace 하나에 연결한다
내가 권하는 매핑은 단순하다.
· Jira 티켓: KAN-146
· Git 브랜치: feature/KAN-146
· worktree: DDD-13-iOeS-iOS-KAN-146/
· cmux workspace: KAN-146
· AI 세션: 해당 worktree에서 실행한 Claude Code 또는 Codex
cmux workspace 안에서는 pane을 역할별로 나눌 수 있다.
· Pane 1: Claude Code 또는 Codex
· Pane 2: xcodebuild와 테스트
· Pane 3: Git 상태, 로그, 보조 명령
이 구조에서는 폴더를 바꾸는 일이 곧 작업을 바꾸는 일이고, cmux workspace를 바꾸는 일이 곧 티켓을 바꾸는 일이 된다.
KAN-146 구현 에이전트가 빌드하는 동안 KAN-145 workspace로 이동해 리뷰 코멘트를 반영할 수 있다. 두 작업은 다른 브랜치와 파일, index를 사용하므로 서로의 변경 사항을 밟지 않는다. 다시 KAN-146으로 돌아왔을 때도 그 worktree의 생성 파일과 빌드 상태가 그대로 남아 있다.
알림이 병렬 작업의 polling을 줄인다
AI 에이전트를 여러 개 띄우면 사용자가 각 세션을 돌아다니며 끝났는지 확인하게 된다. 에이전트가 병렬로 일하는 동안 사람이 순차적으로 polling하는 셈이다.
cmux는 알림이 도착한 workspace에 읽지 않음 표시를 남기고, 알림 패널에서 해당 workspace로 이동할 수 있다. 에이전트나 빌드 스크립트가 CLI를 호출하는 방식도 제공한다.
예를 들어 빌드 성공 시 cmux notify --title "KAN-146" --subtitle "Build" --body "검증 빌드 성공"을 실행하고, 실패 시에는 본문을 빌드 실패: 로그 확인 필요로 바꿔 알릴 수 있다.
이제 계속 터미널을 순회할 필요가 없다. 구현을 맡긴 뒤 다른 일을 하다가 완료, 실패, 입력 요청이 표시된 workspace만 확인하면 된다.
병렬 개발에서 중요한 것은 에이전트를 많이 실행하는 능력보다 사람의 판단이 필요한 순간을 빠르게 발견하는 능력이다.
실제 하루의 흐름은 이렇게 바뀐다
예를 들어 KAN-145 PR이 리뷰 대기 중이고 다음 티켓 KAN-146을 시작한다고 해보자.
단계 1 — 개인 스킬로 KAN-146 worktree를 생성하고 검증 빌드까지 완료한다.
단계 2 — cmux에 KAN-146 workspace를 열고 해당 폴더에서 AI 에이전트를 실행한다.
단계 3 — 구현 작업을 맡긴 뒤 KAN-145 workspace로 이동한다.
단계 4 — 리뷰 코멘트가 도착하면 KAN-145 폴더에서 수정하고 push한다.
단계 5 — KAN-146의 빌드 완료나 입력 요청 알림이 오면 그 workspace로 돌아간다.
이 흐름에는 stash도, 브랜치 checkout도, 매번 반복하는 프로젝트 재생성도 없다. 개발자는 브랜치 상태를 머릿속에 들고 다니는 대신, 티켓 이름이 붙은 폴더와 workspace를 이동한다.
종료 절차도 작업의 일부다
PR이 머지된 뒤에는 worktree를 정리한다.
단계 1 — 메인 클론으로 이동한다: cd DDD-13-iOeS-iOS-develop
단계 2 — 머지된 develop을 받는다: git pull --ff-only origin develop
단계 3 — worktree를 제거한다: git worktree remove ../DDD-13-iOeS-iOS-KAN-145
단계 4 — 로컬 기능 브랜치를 제거한다: git branch -d feature/KAN-145
현재 연결된 worktree는 git worktree list로 언제든 확인할 수 있다.
폴더를 Finder에서 바로 삭제하는 것은 피해야 한다. Git 저장소에 worktree 관리 정보가 남기 때문이다. 이미 폴더를 직접 지웠다면 git worktree prune으로 오래된 메타데이터를 정리할 수 있다.
cmux workspace도 같은 시점에 닫으면 열린 작업 목록이 실제 진행 중인 티켓 목록과 일치한다.
적용 전에 알아둘 트레이드오프
디스크 사용량은 늘어난다
Git 객체는 공유하지만 checkout된 파일과 빌드 산출물은 worktree마다 생긴다. 특히 iOS 프로젝트는 DerivedData와 Tuist 관련 캐시 때문에 worktree 하나가 수 GB를 차지할 수 있다.
브랜치 전환 시간을 줄이는 대신 디스크를 사용한다. 머지된 티켓을 바로 정리하는 습관이 필요하다.
같은 브랜치를 여러 worktree에 보통 중복 checkout할 수 없다
Git은 이미 다른 worktree에서 사용 중인 브랜치를 다시 checkout하지 않도록 막는다. 티켓과 브랜치, 폴더를 1:1로 유지하면 혼란을 피할 수 있다.
포트와 외부 자원은 별도로 격리해야 한다
worktree는 파일과 Git 상태를 나누지만 localhost 포트, 시뮬레이터, 데이터베이스, 키체인까지 자동으로 나누지는 않는다. 여러 앱을 동시에 실행한다면 포트와 런타임 자원의 소유권 규칙도 정해야 한다.
자동화는 실패를 숨기면 안 된다
worktree 생성 스킬이 모든 오류를 무시하고 “완료”라고 말하면 수동 명령보다 위험하다. 단계별 종료 코드를 확인하고, 실패한 위치와 다음 조치를 보여주며, 진단에 필요한 상태를 보존해야 한다.
정리: 격리, 관찰, 자동화를 분리한다
AI 병렬 개발 환경은 에이전트 도구 하나를 설치한다고 완성되지 않았다. 내가 정착한 방식은 역할을 분리하는 것이었다.
· 브랜치와 파일 충돌: git worktree
· Xcode와 Tuist 초기 준비: 개인 스킬
· 여러 터미널과 에이전트 탐색: cmux workspace와 pane
· 완료, 실패, 입력 요청 발견: cmux 알림
· 작업 종료와 공간 회수: git worktree remove와 workspace 정리
핵심은 두 문장으로 줄일 수 있다.
Git의 작업 단위는 브랜치지만, 사람과 AI 에이전트의 작업 단위는 디렉터리다.
병렬 실행의 다음 문제는 더 많은 에이전트가 아니라, 격리된 세션을 얼마나 적은 주의력으로 운영하느냐다.
git worktree가 첫 번째 문제를 해결하고 cmux가 두 번째 문제를 다룬다. 여기에 프로젝트 특유의 준비 절차를 실행 가능한 스킬로 묶으면, 티켓 번호 하나로 독립된 개발 환경을 만들고 여러 작업을 안전하게 오갈 수 있다.
참고 자료
반응형'Dev' 카테고리의 다른 글
[AI] iOS API 디버깅하기: AI와 만든 핑퐁 테스트 워크플로우 (0) 2026.07.23 [AI] UI를 개발할 때 화면 하나를 TDD 3단계로 강제한 방법 (0) 2026.07.23 youtube 요약 n8n 워크플로우 구축기 (0) 2026.05.21 Claude Desktop로 WWDC 요약하기 (Puppeteer, MCP) (0) 2025.04.11 [jekyll] 로컬에서 구동시 Dependency Error 문제 해결하기 (0) 2024.03.21