Skip to content

How to Prevent File Conflicts Between AI Coding Agents: Git Worktrees vs File Locking

How can multiple AI coding agents work on the same codebase without stepping on each other’s toes?

I ran into this problem last month. I had three Claude Code agents running in parallel - one refactoring the authentication module, one updating the database layer, and one working on API endpoints. All three tried to edit src/types/index.ts at the same time. The result? Two agents’ changes got overwritten, and I spent an hour untangling the mess.

After researching how other teams handle this, I found two main approaches: file locking and git worktrees. Each has its place, but they solve the problem differently.

The Problem: Parallel AI Agents, One Codebase

When you run multiple AI coding agents simultaneously, they all see the same file system. If Agent 1 and Agent 2 both try to edit auth.py, one of them loses.

What happens without coordination
Agent 1 reads auth.py (10:00:00)
Agent 2 reads auth.py (10:00:01)
Agent 1 writes auth.py with changes (10:00:02)
Agent 2 writes auth.py with DIFFERENT changes (10:00:03)
Agent 1's changes are GONE

This isn’t theoretical. It happened to me, and I’ve seen it discussed on Reddit where someone reverse-engineered Claude Code to build a better orchestrator. The coordination problem is real.

Approach A: File Locking

The simpler solution. Each agent claims exclusive access to files before editing.

How it works:

file_lock_manager.py
class FileLockManager:
def __init__(self):
self.locked_files = {}
def acquire(self, agent_id: str, file_path: str) -> bool:
if file_path in self.locked_files:
return False # Already locked by another agent
self.locked_files[file_path] = agent_id
return True
def release(self, agent_id: str, file_path: str):
if self.locked_files.get(file_path) == agent_id:
del self.locked_files[file_path]
def is_locked(self, file_path: str) -> bool:
return file_path in self.locked_files

Usage in an agent:

agent_usage.py
async def safe_edit(agent_id: str, file_path: str, changes: dict):
if not lock_manager.acquire(agent_id, file_path):
raise FileLockError(f"{file_path} is locked by another agent")
try:
current_content = read_file(file_path)
updated_content = apply_changes(current_content, changes)
write_file(file_path, updated_content)
finally:
lock_manager.release(agent_id, file_path)

With retry logic for busy files:

edit_with_retry.py
async def edit_with_retry(agent_id: str, file_path: str, changes: dict, max_retries: int = 3):
for attempt in range(max_retries):
if lock_manager.acquire(agent_id, file_path):
try:
return await apply_file_changes(file_path, changes)
finally:
lock_manager.release(agent_id, file_path)
# Exponential backoff before retry
await asyncio.sleep(2 ** attempt)
raise FileLockTimeoutError(f"Could not acquire lock on {file_path} after {max_retries} attempts")

Pros:

  • Simple to implement and understand
  • No merge conflicts to resolve
  • Works well for naturally file-disjoint tasks

Cons:

  • Limits parallelism - if two agents need the same file, one waits
  • Can create bottlenecks on shared files like types/index.ts
  • Risk of deadlocks if Agent 1 holds file A and needs file B while Agent 2 holds file B and needs file A

Claude Code’s built-in approach uses a variation of this. The Reddit discussion mentioned: “tasks have to be file-disjoint. If two agents need to touch the same file, one has to wait.”

Approach B: Git Worktrees

This is what the custom agent system (CAS) described in the Reddit thread uses. Each agent gets its own isolated copy of the repository on its own branch.

How it works:

Git worktree architecture
main-repo/ # Original repo, main branch
├── src/
└── ...
.worktrees/
├── agent-1/ # Agent 1's isolated copy
│ ├── src/ # Full copy, own branch
│ └── ...
├── agent-2/ # Agent 2's isolated copy
│ ├── src/
│ └── ...
└── agent-3/ # Agent 3's isolated copy
├── src/
└── ...

Setting up worktrees:

setup_worktrees.sh
# Create worktree for Agent 1 on its own branch
git worktree add .worktrees/agent-1 -b agent-1-task
# Create worktree for Agent 2
git worktree add .worktrees/agent-2 -b agent-2-task
# Create worktree for Agent 3
git worktree add .worktrees/agent-3 -b agent-3-task
# List all worktrees
git worktree list

Each agent works independently:

agent_workflow.sh
# Agent 1 works in its isolated copy
cd .worktrees/agent-1
# ... agent makes changes to any files ...
git add .
git commit -m "Agent 1: Refactor authentication"
# Agent 2 works in its own copy
cd ../agent-2
# ... agent makes changes to the SAME files ...
git add .
git commit -m "Agent 2: Update database layer"

Supervisor merges all branches:

merge_supervisor.sh
# Back to main repo
cd ../..
# Merge Agent 1's changes
git merge agent-1-task
# Merge Agent 2's changes (may have conflicts!)
git merge agent-2-task
# Resolve conflicts manually or with AI assistance
# Merge Agent 3's changes
git merge agent-3-task
# Clean up worktrees after merge
git worktree remove .worktrees/agent-1
git worktree remove .worktrees/agent-2
git worktree remove .worktrees/agent-3

Pros:

  • True parallelism - all agents can edit the same file simultaneously
  • No waiting for locks
  • Natural integration with git workflow

Cons:

  • Merge conflicts must be resolved by a supervisor (human or AI)
  • More disk space (multiple repo copies)
  • More complex setup

The Reddit poster noted: “each worker gets its own git worktree - a full copy of the repo on its own branch. Three agents can edit the same file at the same time.”

Quick Comparison

FeatureFile LockingGit Worktrees
ParallelismLimited (file-dependent)Full
Merge ConflictsNeverPossible (need resolution)
Setup ComplexityLowMedium
Disk UsageLowHigher (multiple copies)
DebuggingEasy (one source of truth)Harder (multiple branches)
Best ForSmall teams, disjoint tasksLarge parallel tasks, refactoring

When to Use Each Approach

After experimenting with both, here’s my decision guide:

Use file locking when:

  • Your agents work on naturally file-disjoint tasks
  • You have a small number of agents (2-3)
  • Simplicity matters more than speed
  • You want to avoid merge conflicts entirely

Use git worktrees when:

  • Multiple agents need to edit the same files
  • You have a supervisor (human or AI) to handle merges
  • Parallelism is critical for productivity
  • You’re doing large-scale refactoring

My Recommendation

Start with file locking. It’s simple and works well for most use cases.

When you hit the ceiling - when agents are waiting too often, when shared files become bottlenecks - graduate to worktrees. But have a merge strategy ready. Either:

  1. Human reviewer: You review and merge each branch manually
  2. AI supervisor: A separate AI agent trained to resolve merge conflicts
  3. Sequential merge: Merge branches one at a time, fixing conflicts as they appear

What I’m Using Now

For my project, I use a hybrid approach:

hybrid_approach.py
class HybridCoordinator:
def __init__(self):
self.lock_manager = FileLockManager()
self.worktree_dir = ".worktrees"
def assign_task(self, task: Task) -> WorkContext:
# Check if task files overlap with running tasks
if self._has_file_overlap(task.files):
# Use worktree for overlapping files
return self._create_worktree(task)
else:
# Use file locking for disjoint files
return self._create_locked_context(task)
def _has_file_overlap(self, files: list[str]) -> bool:
return any(
self.lock_manager.is_locked(f) or f in self._active_worktree_files()
for f in files
)

This gives me the simplicity of file locking for most tasks, with worktrees as an escape hatch when conflicts are unavoidable.

The key insight: coordination strategy should match your workload. Don’t over-engineer for file-disjoint tasks, but don’t fight merge conflicts manually if you can avoid them.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments