Field Log · Entry

Codex·Claude·OpenCode 동시에 쓰기 — Git worktree·branch·workspace 차이와 충돌 방지

Codex Claude OpenCode가 서로 다른 workspace worktree branch에서 작업한 뒤 하나의 Git 저장소로 통합되는 구조

Codex, Claude Code, OpenCode를 여러 개 열어 한꺼번에 기능을 맡기면 처음에는 개발 속도가 몇 배 빨라진 것처럼 보입니다. 한쪽은 화면을 만들고, 한쪽은 API를 고치고, 다른 한쪽은 테스트를 작성합니다.

저도 그렇게 시작했습니다. 그런데 에이전트들이 같은 프로젝트 폴더에서 동시에 파일을 수정하자 문제가 생겼습니다. 한 에이전트가 읽은 파일을 다른 에이전트가 먼저 바꾸고, 아직 작업 중이던 수정이 덮어써지거나 섞였습니다. 낯선 변경을 발견한 에이전트가 안전을 위해 작업을 멈추기도 했습니다.

처음에는 이것을 모델이나 프롬프트의 문제로 생각하기 쉽습니다. 실제 원인은 더 단순했습니다.

여러 에이전트를 열었지만, 작업 공간은 하나뿐이었다.

해결하려면 branch, worktree, workspace가 각각 무엇을 나누는지 구분해야 합니다. 결론부터 말하면 동시 쓰기 작업의 기본 단위는 다음과 같습니다.

한 에이전트 = 한 작업 = 한 workspace = 한 worktree = 한 branch = 한 PR

브랜치만 여러 개 만드는 것으로는 부족합니다. workspace는 에이전트의 시야를, worktree는 실제 파일을, branch는 커밋 이력을 분리합니다.

세 용어를 한 문장씩 정의하면

  • Git branch: 이 작업의 커밋이 어느 이력으로 이어질지 정하는 이동 가능한 포인터
  • Git worktree: 특정 브랜치의 파일을 실제로 펼쳐 놓고 수정하는 독립 디렉터리
  • Workspace: 에디터나 AI 코딩 도구가 현재 프로젝트로 인식하고 읽고 쓰는 폴더·설정·세션 범위

이 세 개는 경쟁 관계가 아닙니다. 서로 다른 층을 담당하므로 동시에 사용해야 합니다.

Codex Claude OpenCode가 각자의 workspace worktree branch에서 작업하고 PR 단위로 통합되는 세 겹의 격리 구조

여기서 workspace는 Git의 공식 객체가 아닙니다. 도구마다 의미가 조금씩 다릅니다. 예를 들어 VS Code는 한 창에서 연 폴더 하나 또는 여러 폴더의 집합을 workspace라고 정의합니다. 이 글에서는 더 넓게, Codex·Claude Code·OpenCode가 작업 루트로 삼는 폴더와 그 세션의 문맥이라는 뜻으로 사용하겠습니다.

1. Branch는 파일이 아니라 이력을 나눈다

Git 공식 문서에서 branch는 커밋을 가리키는 “가볍고 이동 가능한 포인터”입니다. 새 브랜치를 만드는 일은 프로젝트 폴더를 한 벌 더 복사하는 일이 아닙니다.

git switch -c feat/search

이 명령을 실행하면 feat/search라는 새 커밋 이력이 시작됩니다. 하지만 현재 화면에 보이는 파일, Git index, 빌드 결과물이 놓인 작업 디렉터리는 여전히 하나입니다.

그래서 같은 폴더에 터미널 두 개를 열고 다음처럼 작업하면 브랜치가 보호막이 되어 주지 못합니다.

/projects/my-app  ← 실제 폴더는 하나
  ├─ Codex 세션: feat/search라고 생각하고 작업
  └─ Claude 세션: fix/auth로 전환하고 작업

Claude 세션이 git switch fix/auth를 실행하는 순간, 같은 폴더를 보고 있던 Codex 세션의 파일도 바뀝니다. Codex가 조금 전에 읽은 코드와 디스크의 현재 코드가 달라집니다. 두 세션이 같은 파일을 저장하거나 git add를 실행하면 수정과 index가 섞일 수 있습니다.

즉, branch가 답하는 질문은 **“이 커밋은 어느 줄기에 기록되는가?”**입니다. **“두 에이전트가 서로 다른 파일 복사본을 보고 있는가?”**에는 답하지 않습니다.

Branch만으로 충분한 상황

  • 쓰기 권한을 가진 에이전트가 한 번에 하나뿐일 때
  • 한 작업을 끝내고 커밋한 뒤 다음 작업을 순차적으로 시작할 때
  • 같은 workspace의 다른 세션이 읽기 전용 조사·리뷰만 할 때
  • 짧은 실험을 하고 바로 버릴 때

동시에 두 세션이 코드를 쓴다면 branch만으로는 부족합니다.

2. Worktree는 실제 파일과 index를 나눈다

git worktree는 하나의 Git 저장소에 여러 working tree를 연결합니다. 각 worktree는 서로 다른 경로에 파일을 펼치고, 자기 HEAD와 index를 가집니다. Git object와 일반적인 ref는 공유하므로 저장소 전체를 여러 번 복제하는 것보다 가볍습니다.

하나의 Git 저장소
  ├─ ~/worktrees/my-app-search/  → branch: feat/search
  ├─ ~/worktrees/my-app-auth/    → branch: fix/auth
  └─ ~/worktrees/my-app-docs/    → branch: docs/runbook

이제 Codex가 my-app-search/src/app.ts를 고치고 Claude가 my-app-auth/src/app.ts를 고쳐도 두 파일은 물리적으로 다른 경로에 있습니다. 한 에이전트의 미커밋 변경, staging 상태, untracked 파일이 다른 worktree에 갑자기 나타나지 않습니다.

Git은 같은 로컬 브랜치를 여러 worktree에 동시에 checkout하는 것도 기본적으로 거부합니다. 이 제약은 멀티 에이전트 작업에서 실수를 빨리 드러내는 유용한 안전장치입니다.

Worktree가 해결하는 것

  • 한 에이전트가 저장한 파일이 다른 에이전트의 작업 중인 파일을 즉시 덮어쓰는 문제
  • 한 세션의 git switch가 다른 세션의 파일을 바꾸는 문제
  • 서로의 git add와 index가 섞이는 문제
  • 상대 에이전트가 만든 미커밋 파일을 “낯선 변경”으로 감지해 중단하는 문제
  • worktree 내부에 생성되는 node_modules, .venv, dist, 로컬 캐시가 섞이는 문제

Worktree가 해결하지 못하는 것

두 브랜치가 같은 코드 줄을 서로 다르게 수정하면 나중에 merge conflict가 날 수 있습니다. API 생산자는 응답 필드를 userId로 바꾸고 UI 담당자는 여전히 user_id를 기대하는 식의 의미 충돌도 남습니다.

worktree는 “지금 이 순간의 파일 충돌”을 “나중의 명시적인 통합 작업”으로 옮깁니다. 충돌을 없애는 것이 아니라, 작업 도중 몰래 섞이지 않게 만드는 것입니다. 이 차이가 중요합니다.

3. Workspace는 에이전트의 시야를 나눈다

Workspace는 Git 기능이 아니라 도구의 실행 문맥입니다. 보통 다음이 workspace에 묶입니다.

  • 현재 작업 루트와 에이전트가 탐색할 수 있는 파일 범위
  • AGENTS.md, CLAUDE.md 같은 프로젝트 지침
  • 에디터 설정, task, debugger, extension 상태
  • 터미널의 현재 경로와 AI 대화 세션
  • 도구별 권한과 허용된 외부 디렉터리

중요한 점은 workspace가 다른 이름이어도 같은 폴더를 가리키면 파일은 공유된다는 사실입니다.

Codex workspace A  ─┐
                    ├─ /projects/my-app  ← 같은 파일
Claude workspace B ─┘

창을 두 개 띄우고 대화 세션을 따로 만들었다고 해서 디스크가 복제되지는 않습니다. 반대로 각 workspace가 서로 다른 worktree를 루트로 열면 도구의 문맥과 실제 파일이 함께 분리됩니다.

Codex workspace   → ~/worktrees/my-app-search → feat/search
Claude workspace  → ~/worktrees/my-app-auth   → fix/auth
OpenCode workspace→ ~/worktrees/my-app-docs   → docs/runbook

VS Code의 multi-root workspace처럼 한 창에 여러 worktree를 넣을 수도 있습니다. 사람이 변경을 비교할 때는 편하지만, 쓰기 에이전트에게는 경계를 흐릴 수 있습니다. 코드를 수정하는 에이전트에는 single-root workspace 하나만 주는 편이 안전합니다.

Branch·worktree·workspace 차이

구분BranchWorktreeWorkspace
소속GitGit에디터·AI 도구
핵심 질문커밋이 어느 이력에 쌓이는가?어느 디렉터리의 파일을 수정하는가?도구가 무엇을 보고 어떤 설정으로 일하는가?
주로 분리하는 것커밋 이력파일·HEAD·index폴더 범위·설정·세션 문맥
같은 파일 즉시 덮어쓰기 방지단독으로는 불가가능다른 worktree를 가리킬 때만 가능
최종 merge conflict 방지불가불가불가
동시 쓰기에 단독 사용부적절branch와 함께 사용worktree와 함께 사용
대표 동작git switch -cgit worktree add각 도구에서 별도 폴더 열기

한 줄로 줄이면 다음과 같습니다.

workspace = 에이전트의 시야
worktree  = 에이전트의 책상
branch    = 에이전트의 작업 이력

시야만 나누고 같은 책상을 쓰면 종이가 섞입니다. 책상만 나누고 같은 이름의 작업 이력을 공유하면 통합 책임이 모호해집니다. 세 가지를 같은 단위로 맞춰야 합니다.

제가 겪은 “수정 중단”은 왜 일어났나

코딩 에이전트가 파일을 수정할 때는 대체로 다음 순서를 밟습니다.

1. 파일 읽기
2. 변경 계획 수립
3. patch 작성
4. 현재 파일에 patch 적용
5. diff와 test 확인

에이전트 A가 1~3번을 수행하는 사이 에이전트 B가 같은 파일을 저장하면, A가 기억하는 원문과 현재 파일이 달라집니다. 이때 가능한 결과는 세 가지입니다.

  1. patch가 실패해 에이전트가 다시 읽는다.
  2. 운 나쁘게 patch가 적용되지만 B의 의도를 훼손한다.
  3. 예상하지 못한 변경을 발견하고 안전을 위해 사용자에게 보고한 뒤 멈춘다.

세 번째는 에이전트가 일을 못해서가 아니라 남의 변경을 덮어쓰지 않기 위한 올바른 방어 행동일 수 있습니다. 따라서 “중간에 멈추지 마”라고 프롬프트를 강화하는 것보다 애초에 파일 경로를 분리하는 편이 낫습니다.

실전 구성: 세 에이전트에 세 worktree 만들기

기준 브랜치가 origin/main이라고 가정하겠습니다. 실제 프로젝트의 기본 브랜치 이름에 맞게 바꾸면 됩니다.

1단계: 기준 상태를 갱신한다

git fetch origin
git status --short --branch
git worktree list

현재 폴더에 미커밋 변경이 있다면 그대로 두고, 새 worktree는 명시한 기준 커밋에서 만듭니다. 다른 작업을 깨끗하게 만들겠다는 이유로 reset --hard를 실행하면 안 됩니다.

2단계: 작업별 branch와 worktree를 동시에 만든다

git worktree add \
  -b agent/search-ui \
  ~/worktrees/my-app-search-ui \
  origin/main

git worktree add \
  -b agent/auth-api \
  ~/worktrees/my-app-auth-api \
  origin/main

git worktree add \
  -b agent/release-docs \
  ~/worktrees/my-app-release-docs \
  origin/main

-b 뒤는 새 branch 이름, 그다음은 새 디렉터리, 마지막은 시작 기준입니다. 경로와 branch 이름에 작업 목적을 넣으면 어느 세션이 무엇을 맡았는지 찾기 쉽습니다.

3단계: 각 경로에서 에이전트를 실행한다

cd ~/worktrees/my-app-search-ui
codex
cd ~/worktrees/my-app-auth-api
claude
cd ~/worktrees/my-app-release-docs
opencode

핵심은 어떤 제품을 쓰느냐가 아니라 프로세스의 현재 경로가 서로 다르냐입니다. 실행 직후 각 세션에 다음 세 명령을 확인하게 하면 잘못된 폴더에서 시작한 실수를 줄일 수 있습니다.

git rev-parse --show-toplevel
git branch --show-current
git status --short --branch

4단계: 작업 계약을 함께 준다

Worktree는 파일을 분리하지만 업무 범위까지 정해 주지는 않습니다. 각 에이전트의 요청에 최소한 다음 정보를 넣습니다.

task: 검색 결과 화면 구현
branch: agent/search-ui
worktree: ~/worktrees/my-app-search-ui
owned_paths:
  - src/pages/search/**
  - src/components/Search*
do_not_change:
  - src/api/**
done_when:
  - npm test 통과
  - npm run build 통과
handoff:
  - 변경 파일
  - 실행한 검증
  - 남은 위험

owned_paths는 Git 권한 장치가 아니라 통합 충돌을 줄이는 합의입니다. 서로 다른 worktree에서 같은 파일을 수정할 수는 있으므로, 파일·컴포넌트·API 계약의 책임도 나눠야 합니다.

5단계: 한 명이 순서대로 통합한다

각 에이전트가 자기 branch에 결과를 남기면 사람 또는 통합 담당 에이전트 한 명이 다음을 확인합니다.

1. branch별 diff와 test 결과 검토
2. 기준 branch의 최신 변경 반영
3. 한 branch씩 merge 또는 PR 반영
4. 매 통합 뒤 전체 test 실행
5. 다음 branch에서 생긴 conflict와 계약 변경 해결

여러 에이전트에게 서로의 branch를 동시에 merge하라고 맡기면 통합 지점에서 다시 경쟁 상태가 생깁니다. 병렬화는 구현까지, 최종 통합은 직렬화하는 편이 안정적입니다.

6단계: merge 뒤 안전하게 정리한다

먼저 대상 worktree가 깨끗하고 branch가 실제로 통합됐는지 확인합니다.

git -C ~/worktrees/my-app-search-ui status --short
git worktree list

확인이 끝난 뒤 Git 명령으로 제거합니다.

git worktree remove ~/worktrees/my-app-search-ui
git branch -d agent/search-ui
git worktree prune

미커밋 파일이 있는 worktree를 강제로 지우거나 디렉터리를 먼저 삭제하지 않습니다. 아직 필요한 결과가 있으면 commit·patch·백업 중 하나로 보존한 뒤 정리해야 합니다.

병렬 작업에서 남는 세 종류의 충돌

Worktree를 도입해도 운영 규칙은 필요합니다. 충돌을 세 종류로 나누면 무엇을 추가로 격리해야 하는지 보입니다.

1. 파일 충돌

같은 디렉터리의 파일과 index를 동시에 건드리는 문제입니다.

대응: 에이전트별 worktree와 branch.

2. 통합 충돌

서로 다른 디렉터리에서 작업했지만 같은 줄, 같은 함수 계약, 같은 schema를 다르게 바꾼 문제입니다.

대응: owned_paths, API 계약 선행, 작은 PR, 통합 담당자, branch별 순차 검증.

3. 실행 자원 충돌

파일은 분리됐지만 다음 자원은 여전히 공유될 수 있습니다.

  • 같은 dev server port
  • 같은 로컬 database와 migration
  • 같은 Redis key prefix나 queue
  • 같은 Docker Compose project 이름
  • 저장소 밖의 공용 output 디렉터리
  • 전역 package cache와 자격 증명

대응: worktree별 port, database/schema, Compose project name, 임시 경로를 부여합니다.

search-ui: PORT=3101, DB=app_agent_search
auth-api:  PORT=3102, DB=app_agent_auth
docs:      PORT=3103, DB 없음

파일 격리만 하고 세 에이전트가 같은 개발 DB에 migration을 실행하면 worktree 밖에서 다시 충돌합니다. 격리 경계는 Git에서 끝나지 않습니다.

어떤 상황에 무엇을 선택할까

상황적절한 선택이유
에이전트 한 명이 순차 작업branch이력 분리만으로 충분
여러 세션이 읽기 전용 조사workspace 분리 또는 같은 workspace파일을 쓰지 않으므로 충돌 위험이 낮음
같은 저장소에서 2개 이상 기능 동시 구현worktree + branch + 별도 workspace파일·index·이력·문맥을 모두 분리
기능 작업 중 긴급 hotfix새 worktree + hotfix branch진행 중인 미커밋 변경을 치우지 않고 전환
프런트·백엔드 등 여러 저장소를 함께 보기multi-root workspace관련 프로젝트를 한 시야에 묶기 좋음
서로 다른 런타임·OS·보안 경계가 필요별도 clone 또는 containerGit 파일보다 강한 환경 격리가 필요
submodule을 많이 쓰는 superproject별도 clone도 검토Git 공식 문서도 multiple checkout의 submodule 지원이 불완전하다고 안내

별도 clone이 worktree보다 나은 경우

Worktree들은 Git object와 ref를 공유합니다. 대부분의 기능 개발에는 효율적이지만 다음 상황에서는 독립 clone이나 container가 더 단순합니다.

  • 서로 다른 Git config나 credential을 써야 할 때
  • 한 작업이 repository maintenance나 ref를 강하게 조작할 때
  • 신뢰하지 않는 script를 실행할 때
  • dependency와 native build 환경까지 완전히 분리해야 할 때
  • submodule 조합을 worktree별로 크게 다르게 운용할 때

대신 clone은 object와 dependency를 중복 저장하고 원격 동기화 지점도 늘어납니다. 같은 저장소의 일반적인 병렬 기능 개발은 worktree, 보안·런타임까지 강하게 분리해야 하면 clone 또는 container가 적절합니다.

선택 기준을 결정 트리로 줄이면

두 작업이 동시에 파일을 쓰는가?
  ├─ 아니오 → branch만으로도 충분
  └─ 예
      ├─ 같은 Git 저장소인가?
      │   ├─ 예 → 작업마다 worktree + branch + workspace
      │   └─ 아니오 → 저장소별 workspace, 필요하면 multi-root
      └─ port·DB·container도 공유하는가?
          ├─ 예 → 실행 자원까지 이름과 경로 분리
          └─ 신뢰·OS 경계까지 필요한가? → 별도 clone/container

제가 정착한 운영 규칙

여러 도구를 동시에 쓰면서 가장 효과가 컸던 규칙은 복잡한 오케스트레이터가 아니었습니다.

  1. 메인 checkout은 통합 전용으로 둔다. 쓰기 에이전트를 바로 실행하지 않는다.
  2. 한 에이전트에 한 worktree와 한 branch만 준다.
  3. 각 에이전트는 자기 worktree에서 branch를 바꾸지 않는다.
  4. 작업 시작과 종료 때 git status와 현재 branch를 확인한다.
  5. 파일 소유 범위와 완료 검증을 요청에 함께 적는다.
  6. 에이전트는 구현 결과를 넘기고, 통합 담당자만 merge한다.
  7. worktree는 작업이 통합되고 깨끗한 것을 확인한 뒤 제거한다.

여기에 PR까지 사용하면 리뷰와 통합 단위도 자연스럽게 맞춰집니다.

1 agent
  = 1 task
  = 1 workspace
  = 1 worktree
  = 1 branch
  = 1 PR

이 규칙은 에이전트 수를 늘리기 위한 기술이라기보다 작업 중인 변경의 소유자를 분명하게 만드는 기술입니다.

자주 묻는 질문

Worktree마다 저장소를 다시 clone해야 하나요?

아닙니다. 연결된 worktree는 한 저장소의 Git object를 공유하고 실제 working tree만 별도 경로에 둡니다. 다만 node_modules, .venv처럼 Git이 추적하지 않는 dependency는 각 worktree에 따로 설치할 수 있으므로 디스크 사용량은 늘어날 수 있습니다.

한 branch를 두 worktree에서 동시에 써도 되나요?

일반적인 병렬 작업에서는 쓰지 않는 편이 맞습니다. Git도 같은 로컬 branch의 중복 checkout을 기본적으로 막습니다. 에이전트마다 고유 branch를 만드세요.

Worktree만 만들고 branch는 공유하면 안 되나요?

기술적으로 detached HEAD 같은 방식도 가능하지만, 완료된 변경의 소유와 통합 경로가 흐려집니다. 버릴 실험이 아니라면 worktree를 고유 branch와 묶는 것이 관리하기 쉽습니다.

Workspace를 여러 개 만들면 worktree는 없어도 되나요?

각 workspace가 같은 폴더를 가리키면 안 됩니다. 이름과 세션만 분리됐을 뿐 실제 파일은 같습니다. 각 workspace의 root가 서로 다른 worktree인지 확인해야 합니다.

Worktree를 쓰면 merge conflict가 사라지나요?

아닙니다. 작업 도중의 물리적 덮어쓰기를 막을 뿐입니다. 같은 코드나 계약을 다르게 바꾸면 통합 단계에서 conflict가 나타납니다. 그때 명시적으로 해결할 수 있다는 것이 worktree의 가치입니다.

마무리

제가 여러 Codex·Claude·OpenCode 세션을 열었을 때 생긴 충돌은 “에이전트가 너무 많아서”가 아니었습니다. 여러 작업자가 하나의 working directory와 index를 공유한 것이 문제였습니다.

  • branch는 커밋 이력을 나눕니다.
  • worktree는 실제 파일과 index를 나눕니다.
  • workspace는 도구의 시야와 세션 문맥을 나눕니다.

동시에 코드를 쓰게 하려면 셋 중 하나를 고르는 것이 아니라 세 경계를 맞춰야 합니다. 그리고 마지막 통합은 한 명이 순서대로 책임져야 합니다.

처음부터 에이전트 다섯 개를 운영할 필요는 없습니다. 다음 작업에서 Codex와 Claude 두 개만 서로 다른 worktree에 열어 보세요. “누가 내 파일을 바꿨지?”라는 질문이 사라지는 것만으로도 병렬 작업의 안정성이 크게 달라집니다.

참고자료