AI 에이전트 두 개를 켜면 일이 두 배 빨라질 것 같지만, 같은 폴더에서 동시에 파일을 고치면 오히려 결과를 믿기 어려워질 수 있습니다. 한쪽이 읽은 직후 다른 쪽이 파일을 바꾸면, 각자가 생각하는 코드 상태부터 달라집니다. 터미널 창을 나누는 것만으로는 이 문제가 해결되지 않습니다.
Git worktree는 같은 저장소의 서로 다른 브랜치를 별도 폴더에서 작업하도록 만듭니다. 병렬 코딩의 편집 충돌을 줄이는 출발점이지만, 최종 통합과 외부 자원까지 대신 분리해 주지는 않습니다. 담당 범위를 벗어난 변경은 Husky의 커밋 훅으로 검사하고, 두 작업을 합친 결과는 CI에서 검증하면 작업 경계를 더 확실하게 유지할 수 있습니다.
폴더 분리로 줄어드는 충돌과 남는 책임
브랜치는 변경 이력을 가리키고, 작업 폴더는 지금 파일을 읽고 쓰는 장소입니다. 같은 폴더에서 에이전트마다 다른 브랜치로 전환하면 그 폴더의 실제 파일 상태도 바뀝니다. 각 에이전트에게 전용 작업 폴더가 필요한 이유입니다. Git 브랜치와 작업 파일 설명
worktree는 별도 checkout과 인덱스를 제공하면서 저장소의 이력을 공유합니다. 완전히 독립된 저장소 복사본과는 다릅니다. 보통 같은 브랜치를 다른 worktree에서 중복 checkout하려 하면 Git이 거부하므로, 작업별로 브랜치 이름을 다르게 두는 편이 자연스럽습니다. Git worktree 문서
가상의 작은 앱에서 A는 로그인 입력 검증, B는 문의 폼 안내 문구를 맡는다고 가정해 보겠습니다. 담당 파일이 분명하면 동시에 진행하기 쉽습니다. 반면 A가 공통 사용자 타입을 바꾸고 B가 그 타입에 의존한다면, 폴더가 달라도 계약이 맞지 않을 수 있습니다. worktree는 덮어쓰기를 줄여 주지만 서로 다른 가정을 자동으로 합의시키지는 않습니다.
| 구분 | 역할 | 설명 | 예시 |
|---|---|---|---|
| 대화 | 세션을 나누면 판단 맥락이 갈립니다 | 각 에이전트는 전달받은 범위 안에서 일합니다. | A가 바꾼 응답 형식을 B가 자동으로 알 것이라 가정하지 않습니다. |
| 파일 | worktree로 작업 파일을 나눕니다 | 서로 다른 폴더·브랜치에서 변경을 만듭니다. | 로그인 수정과 문의 폼 수정이 각자의 diff로 남습니다. |
| 실행 자원 | 포트와 데이터는 따로 점검합니다 | 같은 호스트와 외부 서비스의 공유 상태는 별도 문제입니다. | 두 개발 서버가 같은 포트나 같은 테스트 DB를 사용합니다. |
깨끗한 시작점에서 두 작업 만들기
아래는 demo-app 저장소에 커밋이 있고, 기준 브랜치가 main이며, 새 폴더와 브랜치 이름이 아직 없다는 전제입니다. 처음에는 원격 연결이 없는 연습용 저장소에서 흐름을 익히는 편이 좋습니다. 실제 작업에서는 미커밋 변경을 임의로 정리하지 말고 먼저 소유자를 확인합니다.
아래 Git·pnpm 명령은 Windows PowerShell·macOS zsh·Linux bash 공통입니다. Git·Node.js·pnpm을 각 운영체제에 설치한 상태에서 실행합니다. 상대 경로는 공통으로 /를 사용하며, .husky/pre-commit 파일 안의 코드는 세 환경 모두 POSIX 셸용입니다. Windows에서는 Git for Windows의 셸을 사용하므로 훅 내용을 PowerShell로 바꾸지 않습니다.
저장소 루트에서 상태와 현재 브랜치를 확인합니다. git status --short에 변경이 보이면 필요한 수정인지 판단해 커밋하거나 별도로 보존한 뒤 진행합니다. 이 예시는 깨끗한 시작점을 의도하므로, 경고를 무시하고 다음 명령을 이어 붙이지 않습니다.
git status --short
git branch --show-current
git worktree list
확인한 main에서 새 브랜치 두 개와 형제 폴더를 만듭니다. 폴더 이름은 자신의 작업 경로로 바꿀 수 있지만, 기존 프로젝트나 다른 worktree와 겹치지 않아야 합니다.
git worktree add -b ai/login-check ../demo-login main
git worktree add -b ai/contact-copy ../demo-contact main
git worktree list
예상되는 상태는 원래 폴더 외에 demo-login과 demo-contact가 각각 다른 브랜치를 가리키는 것입니다. 생성 명령의 의미와 목록 확인법은 Git 공식 문서에 설명돼 있습니다. 실행에 실패하면 브랜치 이름 중복, 경로 존재 여부, 기준 브랜치를 순서대로 확인합니다. --force로 원인을 덮고 진행하지 않습니다.
각 터미널은 맡은 폴더에서 시작합니다. 로그인 담당 터미널에서는 아래 상태를 확인한 다음 에이전트를 실행합니다.
cd ../demo-login
git branch --show-current
git status --short
다른 터미널에서는 demo-contact로 이동해 같은 확인을 합니다. Claude Code에는 claude --worktree 작업이름으로 자체 worktree를 만드는 경로도 있습니다. 수동으로 만든 폴더에서 일반 세션을 여는 방식과 구분해서 하나를 선택합니다. 기본 시작점·재사용·정리 동작은 버전에 따라 달라질 수 있으므로 현재 Claude Code worktree 안내를 확인합니다.
병렬 작업의 책임과 공통 계약
같은 목표를 에이전트 둘에게 주면 독립 구현 비교는 가능하지만, 결과를 둘 다 합쳐야 하는 것은 아닙니다. 속도를 높이는 분업이 목적이라면 작성 대상과 공통 계약을 먼저 정합니다. 예를 들어 로그인 API의 응답 구조는 유지하고, A는 검증 로직과 테스트만, B는 문의 화면의 문구와 접근성 이름만 수정하도록 범위를 적습니다.
다음은 로그인 담당자에게 주는 개념 예시입니다. 실제 경로와 검증 명령은 해당 저장소에서 확인해 넣습니다.
담당: 로그인 입력 검증과 해당 회귀 테스트.
작업 위치: 현재 demo-login worktree.
수정 범위: 합의한 로그인 모듈과 그 테스트 파일.
공통 계약: 공개 응답 형식, 공통 사용자 타입, 의존성 버전은 유지합니다.
범위 밖 변경이 필요하면 먼저 이유와 영향을 보고합니다.
완료 시: 변경 파일, 실행한 검증, 남은 실패, 커밋 ID를 반환합니다.
다른 worktree 수정, 원격 push, 배포는 하지 않습니다.
범위를 좁혀도 예상하지 못한 공통 파일 수정이 필요할 수 있습니다. 그때는 한 에이전트가 계약 변경을 먼저 맡고, 다른 에이전트는 그 결과를 받은 뒤 이어가는 편이 낫습니다. 담당이 둘이라는 이유로 독립적이지 않은 작업을 억지로 동시에 진행하지 않습니다.
| 구분 | 역할 | 설명 | 예시 |
|---|---|---|---|
| 동시 진행 후보 | 서로 다른 화면의 독립 수정 | 공통 타입·의존성·빌드 설정을 유지하는 조건에서 분업합니다. | 로그인 검증과 문의 폼 문구처럼 계약 변화가 없는 수정. |
| 선행 합의 필요 | 공통 타입과 API 변경 | 반환 형식과 오류 의미를 먼저 고정하고 소비자 코드를 수정합니다. | A가 필드 이름을 바꾸는데 B는 이전 필드를 읽는 상황. |
| 한 작성자 권장 | 락파일·마이그레이션 | 프로젝트 전체에 영향을 주는 변경은 통합 담당자를 둡니다. | 서로 다른 의존성 갱신이나 같은 DB 스키마 수정. |
리뷰 담당자는 읽기 전용으로 운영할 수 있습니다. 지적과 직접 수정을 동시에 맡기면 누가 최종 코드를 책임지는지 흐려지기 쉽습니다. 여러 작업의 상태를 보는 방법은 Herdr을 이용한 바이브 코딩에서 이어서 살펴볼 수 있습니다.
Husky로 담당 범위 밖의 커밋 막기
worktree로 폴더를 나눴다면, 다음 단계는 각 작업의 약속을 실행 가능한 검사로 만드는 것입니다. 에이전트에게 “공통 타입은 수정하지 마세요”라고 알려 두는 것에 더해, 실제로 그 파일이 커밋에 들어가면 중단하도록 만들 수 있습니다. 이런 검사 스크립트와 실행 절차를 묶어 작업 하네스로 운영하면 실수를 더 일찍 발견할 수 있습니다.
Husky는 이 하네스를 Git 훅에 연결하는 도구입니다. pre-commit에서 검사 명령이 0이 아닌 종료 코드로 끝나면 Git은 커밋 생성을 중단합니다. 파일 수정 자체를 막거나 두 브랜치의 미래 충돌을 예측하는 기능은 아닙니다. 담당 범위를 벗어난 변경이나 검사 실패가 커밋으로 굳어지는 것을 막는 장치로 이해하면 정확합니다. Git의 pre-commit 동작
1. 공통 검사부터 준비하고 worktree에 전달하기
앞의 예시에서는 로그인 담당이 src/login/과 tests/login/, 문의 폼 담당이 src/contact/와 tests/contact/만 수정하도록 정하겠습니다. 공통 타입, 락파일, DB 마이그레이션, 검사 스크립트는 이 허용 목록에 넣지 않습니다. 변경이 필요하면 통합 담당자와 범위를 다시 합의합니다.
Husky가 없는 Node.js·pnpm 프로젝트라면 공통 기준 브랜치에서 다음과 같이 시작할 수 있습니다. 기존 훅이나 prepare 스크립트가 있으면 먼저 내용을 확인하고 기존 검사를 보존해 연결해야 합니다.
pnpm add -D husky
pnpm exec husky init
초기화는 .husky/pre-commit과 package.json의 준비 스크립트를 설정합니다. 아래 검사 파일과 훅을 공통 기준 커밋에 포함한 뒤 worktree를 나누면 각 작업이 같은 규칙에서 출발합니다. 이미 worktree를 만들었다면 하네스 변경 커밋을 각 브랜치에 반영해야 합니다. 파일을 작성자 폴더 한 곳에만 추가하면 다른 작업 폴더에는 전달되지 않습니다. Husky 시작 안내
각 worktree에서도 프로젝트의 설치 절차를 실행해 의존성과 훅 실행 파일을 준비합니다. 보통 pnpm install 과정의 prepare를 사용하며, 설치 스크립트 실행을 제한하는 환경에서는 별도 설정이 필요합니다. worktree는 Git 설정을 기본적으로 공유하므로 한 작업에서 core.hooksPath를 임의의 절대 경로로 바꾸면 다른 작업에도 영향을 줄 수 있습니다. 각 폴더에서 git config --show-origin --get core.hooksPath로 설정 출처를 확인하고, 실제 커밋 차단까지 확인하는 편이 좋습니다. worktree의 설정 공유, Husky 훅 설정
2. 커밋에 들어갈 파일을 검사하기
다음 파일을 scripts/check-worktree-scope.mjs로 저장합니다. 경로는 앞에서 합의한 가상 프로젝트 구조이며, 실제 프로젝트의 폴더에 맞게 바꿔야 합니다.
import { execFileSync } from 'node:child_process';
const git = (...args) => execFileSync('git', args, { encoding: 'utf8' });
const stop = (message) => {
console.error(message);
process.exit(1);
};
const branch = git('branch', '--show-current').trim();
if (!branch) stop('브랜치가 없는 상태에서는 커밋을 중단합니다.');
// 이 검사는 ai/ 작업 브랜치에 적용합니다.
if (!branch.startsWith('ai/')) process.exit(0);
const rules = {
'ai/login-check': ['src/login/', 'tests/login/'],
'ai/contact-copy': ['src/contact/', 'tests/contact/'],
};
const allowed = rules[branch];
if (!allowed) stop(`담당 범위가 등록되지 않은 브랜치입니다: ${branch}`);
const files = git(
'diff', '--cached', '--name-only', '--no-renames', '-z', '--'
).split('\0').filter(Boolean);
const blocked = files.filter(
(file) => !allowed.some((prefix) => file.startsWith(prefix))
);
if (blocked.length) {
stop(`담당 범위 밖의 변경입니다:\n${blocked.join('\n')}`);
}
// 작업 파일로 실행하는 검사가 다른 내용을 보지 않게 합니다.
try {
git('diff', '--quiet', '--');
} catch {
stop('추적 파일에 스테이징하지 않은 변경이 있습니다. 먼저 정리해 주세요.');
}
console.log(`수정 범위 검사 통과: ${branch}`);
핵심은 작업 폴더 전체가 아니라 git diff --cached로 스테이징된 변경을 읽는 데 있습니다. --no-renames는 이름 변경을 삭제와 추가로 나눠 출발 경로와 도착 경로를 모두 검사하게 합니다. 공통 파일을 담당 폴더로 옮긴 뒤 수정 범위 검사에서 빠져나가는 실수를 줄일 수 있습니다. -z로 파일 이름을 구분하므로 공백이 있는 경로도 한 파일로 처리합니다. Git diff 옵션
이 예시는 ai/로 시작하는 브랜치에만 담당 범위 검사를 적용하고, 등록하지 않은 ai/ 브랜치는 중단합니다. 통합 브랜치 등 다른 이름에서는 범위 검사를 건너뜁니다. 이는 통합 담당자의 작업을 허용하기 위한 운영 규칙이며, 브랜치 이름을 바꿀 권한까지 통제하는 보안 장치는 아닙니다.
마지막 검사는 추적 파일에 스테이징하지 않은 변경이 있으면 멈추도록 구성했습니다. 테스트는 보통 디스크의 파일을 읽지만 커밋은 인덱스의 내용을 기록하므로, 두 상태가 다르면 “검사는 통과했는데 커밋에는 다른 코드가 들어가는” 문제가 생길 수 있기 때문입니다. 부분 스테이징을 자주 사용한다면 이를 허용하는 별도 검사 방식을 설계해야 합니다. 미추적·무시 파일과 외부 DB까지 일치시키는 검사는 아니므로 CI의 깨끗한 체크아웃에서도 검증해야 합니다.
3. 검사 실패를 실제 커밋 중단으로 연결하기
.husky/pre-commit에는 다음처럼 범위 검사를 먼저 연결합니다.
node scripts/check-worktree-scope.mjs || exit 1
프로젝트에 이미 typecheck 명령이 있다면 그 아래에 pnpm run typecheck || exit 1을 추가할 수 있습니다. lint와 관련 테스트도 저장소에서 실제 사용하는 명령을 연결합니다. 훅은 POSIX 셸 문법을 사용하므로 Windows에서 실행하더라도 파일 안에 PowerShell 전용 문법을 섞지 않습니다. 종료 코드를 무시하는 || true를 붙이면 실패를 발견하고도 커밋을 허용하게 됩니다. Husky의 스크립트 연결
로그인 브랜치에서 src/login/validate.ts만 스테이징했다면 범위 검사가 통과합니다. 반대로 pnpm-lock.yaml이나 src/shared/user.ts까지 포함되면 파일 목록을 출력하고 커밋이 중단됩니다. 이때 수정 파일은 남아 있으므로 내용을 잃지 않고 담당자와 조정할 수 있습니다. 훅을 시험할 때는 연습용 저장소에서 커밋 전후의 git rev-parse HEAD를 비교해, 차단된 경우 커밋 ID가 바뀌지 않았는지 확인합니다.
| 구분 | 역할 | 설명 | 예시 |
|---|---|---|---|
| 수정 범위 | 범위 밖 파일이 포함됨 | 로그인 담당이 공통 타입이나 락파일까지 바꾸면 커밋을 중단합니다. | 공통 변경은 통합 담당자에게 넘기거나 범위를 다시 합의합니다. |
| 코드 품질 | 타입·린트·관련 테스트 실패 | 담당 폴더 안의 수정이어도 연결한 품질 검사가 실패하면 중단합니다. | 오류를 고친 뒤 같은 검사를 다시 실행합니다. |
| 통합 결과 | 각자 통과했지만 함께 실패 | 두 브랜치를 합친 상태의 검증이 필요하며 로컬 훅만으로 해결되지 않습니다. | 로그인 결과를 문의 폼이 소비하는 연결 경로를 확인합니다. |
커밋 훅에 모든 검사를 넣으면 작은 커밋에도 시간이 오래 걸릴 수 있습니다. 범위 검사와 빠른 정적 검사는 pre-commit, 무거운 통합 테스트는 CI에 두고 실패 시 병합을 막는 구성이 출발점으로 적절합니다. 훅이 다른 worktree의 파일을 자동 수정하거나 작업 중인 변경을 몰래 옮기도록 만들면 오히려 상태를 추적하기 어려워집니다.
4. 우회 가능한 로컬 훅을 CI로 보완하기
Husky는 로컬에서 끌 수 있고 git commit --no-verify로 pre-commit을 건너뛸 수도 있습니다. 에이전트에게 훅 파일과 설정을 바꿀 권한이 있다면 이 역시 절대적인 강제가 아닙니다. 작업 지침에는 훅 비활성화·검사 삭제·범위 임의 확대를 금지하고, 실패 이유와 필요한 예외를 보고하도록 적는 편이 좋습니다. Husky의 훅 비활성화 동작
중요한 기본 브랜치에는 PR과 필수 상태 검사를 요구하고, 우회 권한도 의도한 범위로 제한합니다. CI는 로컬 훅 실행 여부와 관계없이 검사를 직접 실행해야 합니다. 특히 위 스크립트는 로컬 인덱스와 브랜치를 읽으므로, 그대로 CI에 복사해서는 PR 전체를 검사할 수 없습니다. CI에서는 PR의 기준 커밋과 변경 커밋 사이 파일 목록을 구하고, PR의 작업 역할에 맞는 허용 경로를 적용해야 합니다. 검사 스크립트·정책·워크플로 자체의 변경에도 검토를 요구해야 작업자가 검사 기준을 느슨하게 바꿔 통과하는 일을 줄일 수 있습니다. GitHub 보호 브랜치와 필수 검사
두 PR이 각각 통과한 뒤 서로 다른 순서로 합쳐지는 문제도 남습니다. 기준 브랜치의 최신 변경을 포함한 검사를 요구하거나, 여러 PR이 자주 합쳐진다면 merge queue처럼 통합 예정 상태를 검증하는 방식을 검토할 수 있습니다. worktree는 편집 공간을 나누고, 훅은 각 커밋을 검사하며, CI는 합쳐질 결과를 검사합니다. 세 단계를 함께 운영해야 충돌 가능성과 잘못된 변경이 들어갈 가능성을 함께 줄일 수 있습니다.
환경 변수·포트·DB도 따로 분리하기
새 worktree에는 보통 기존 폴더의 미추적 .env와 설치된 의존성이 그대로 따라오지 않습니다. Claude Code는 선택한 무시 파일을 복사하는 .worktreeinclude도 제공하지만, 어떤 파일을 공유할지 직접 결정해야 합니다. 미추적 파일 처리 비밀 키 파일 전체를 편의상 복제하기보다, 테스트에 필요한 값과 권한만 준비합니다.
두 폴더에 같은 운영 DB 주소를 넣으면 소스 파일은 분리돼도 쓰기 대상은 하나입니다. 이 글의 예시에서는 실제 고객 데이터에 연결하지 않고, 작업별 테스트 데이터나 읽기 전용 환경을 선택합니다. 같은 머신의 개발 서버는 서로 다른 포트를 사용하고, 브라우저 검증도 어느 URL이 어느 worktree의 서버인지 확인합니다. 이 부분은 Git 기능이 아니라 실행 환경을 설계하는 사람의 책임입니다.
Claude Code의 worktree 모드는 주 작업 폴더를 잘못 수정하는 일을 줄이기 위해 편집 대상과 명령의 작업 위치 등을 검사합니다. 다만 이는 도구의 보호 기능이며 운영체제 수준의 격리는 아닙니다. 사용하는 셸과 실행 경로에 따라 검사 범위가 다르므로, 별도 스크립트나 서비스까지 자동으로 격리된다고 가정해서는 안 됩니다. Claude Code의 worktree 보호 범위
설정 파일과 계정 권한이 공유되는 범위도 확인합니다. Git worktree를 보안 샌드박스로 이해해서는 안 됩니다. 에이전트 실행 권한과 파일 접근 통제는 별도로 구성해야 합니다. 폴더가 다르면 다른 폴더에 접근할 수 없다는 가정은 일반 Git 기능에서 성립하지 않습니다.
결과 통합부터 검증과 정리까지
각 담당자는 자기 worktree에서 diff와 관련 테스트를 확인하고, 합의된 파일만 커밋합니다. 통합 담당자는 반환된 커밋과 변경 파일을 검토합니다. '테스트 통과'라는 문장만 받지 말고 어떤 명령과 조건이 통과했는지 확인합니다.
원래 저장소의 깨끗한 상태에서 별도 통합 브랜치를 만들고 결과를 하나씩 합치는 예시입니다. 첫 병합이나 검증에 문제가 생기면 두 번째 병합을 진행하지 않습니다.
git switch -c integrate/ai-work main
git merge --no-ff ai/login-check
로그인 변경과 관련 동작을 확인한 뒤 문의 폼 변경을 합칩니다.
git merge --no-ff ai/contact-copy
git status --short
git diff main...HEAD
충돌이 생기면 어느 파일과 어떤 의도가 겹쳤는지 확인하고 해결해야 합니다. 일단 병합을 중단해야 한다면, 깨끗한 상태에서 시작했다는 전제 아래 git merge --abort로 병합 전 상태로 돌아가는 방법이 있습니다. 미커밋 변경이 섞이면 복원이 어려워질 수 있어 시작 상태 확인이 중요합니다. Git merge 문서
텍스트 충돌 없이 병합됐어도 앱이 정상이라는 뜻은 아닙니다. 로그인한 사용자가 문의 폼을 여는 흐름처럼 두 변경이 만나는 경로를 확인합니다. 공통 타입 검사·관련 테스트·빌드는 프로젝트의 실제 명령으로 실행합니다. 검토 기준은 AI 생성 코드 리뷰 체크리스트에 이어 정리돼 있습니다.
작업 폴더는 결과가 보존되고 에이전트가 종료된 뒤 정리합니다. git worktree list로 대상을 재확인하고 해당 폴더의 미커밋·미추적 파일을 살펴본 다음 git worktree remove를 사용합니다. 삭제가 거부되면 미보존 파일이나 잠금을 확인합니다. 강제 삭제를 기본 절차로 삼지 않습니다.
위 예시를 정리할 때는 원래 저장소 루트에서 아래처럼 확인합니다. 작업 결과를 커밋·통합했고 두 폴더의 에이전트와 개발 서버가 종료됐다는 전제입니다.
git worktree list
git -C ../demo-login status --short
git -C ../demo-contact status --short
변경 목록이 비어 있어도 무시된 파일은 표시되지 않습니다. 필요한 로컬 설정과 결과물을 별도로 보존한 뒤, 목록의 경로가 의도한 두 작업 폴더인지 확인하고 제거합니다.
git worktree remove ../demo-login
git worktree remove ../demo-contact
git worktree list
이 명령은 연결된 작업 폴더를 제거하며 브랜치는 남겨 둡니다. 통합 결과까지 확인한 뒤 필요 없는 브랜치는 git branch -d ai/login-check처럼 따로 정리할 수 있습니다. git worktree prune은 이미 사라진 작업 폴더의 관리 정보를 청소하는 명령으로, 정상적인 폴더 제거 절차를 대신하지 않습니다. worktree 정리 명령
worktree를 도입할 때 첫 목표는 에이전트 수를 늘리는 것이 아닙니다. 두 작업의 시작점·작성 책임·검증 결과를 각각 설명할 수 있게 만드는 것입니다. 작은 독립 수정 둘을 분리해 보고, 병렬 실행으로 아낀 시간보다 통합에 더 오래 걸린다면 작업 경계를 먼저 다시 나눕니다.
VIEW—


