When Two AI Agents Edit the Same File
How we detect, prevent, and resolve conflicts between parallel Claude Code agents editing the same codebase — from session-level tracking to hunk-level git staging.
When Two AI Agents Edit the Same File
The standard approach to running parallel AI agents is git worktrees. Each agent gets its own copy of the repository, works in isolation, and merges back when done. Claude Code supports this natively with --worktree mode. It works. But it comes at a cost: every worktree duplicates the working tree on disk, takes seconds to create, and defers conflict resolution to merge time — when the context of what each agent was doing is already gone.
We wanted to know what happens when agents share a single working tree instead. No copies, no merge step, no disk overhead. Just multiple Claude Code sessions editing the same files in the same repo. The tradeoff is obvious: you can get conflicts. The question is whether tooling can detect them in real-time and resolve them cleanly.
The Problem: Dirty Commits
Here is the scenario that breaks things. Two Claude Code agents are running in parallel on the same repository, each in its own terminal. Agent A is fixing .piano-key sizing in styles.css. Agent B is adding .correction-panel styles to the same styles.css.
Agent A finishes first and commits. The commit includes styles.css — the entire file, including Agent B's uncommitted changes that are sitting in the working tree. Now Agent A's commit contains work it did not do. And when Agent B runs git diff afterward, its changes to styles.css are gone from the diff — because they were already committed by Agent A. Agent B's commit will be incomplete or empty for that file.
Timeline:
1. Agent A edits styles.css lines 877-885
Agent B edits styles.css lines 2060-2264 (parallel, same file)
2. Agent A finishes first, runs: git add styles.css
--> Stages the ENTIRE file, including Agent B's uncommitted lines
3. Agent A commits
--> Commit contains Agent B's work (dirty commit)
4. Agent B runs: git diff styles.css
--> Empty. Its changes were already committed by Agent A.
--> Agent B's own commit will be incomplete.
This is the core problem. git add <file> is file-level. Agent edits are hunk-level. When two agents edit different hunks of the same file, you need hunk-level staging to keep commits clean.
How Callipso Detects the Overlap
Callipso tracks every file each Claude Code session edits through SessionFileTracker. When Claude Code's Write or Edit tool fires, a hook calls recordFileTouch() on the backend, which maintains per-session state:
SessionDiffState
├── sessionId // unique per Claude Code invocation
├── touchedFiles // Set<string> — every file edited in this session
├── committedFiles // Set<string> — files confirmed committed
├── committed // true when ALL files are committed
├── baseCommit // git hash at session start (diff baseline)
├── fileDeltas // Map<file, {added, removed}> — line counts from hooks
└── lastDiff // DiffSummary — latest git diff --numstat output
Before pushing diff state to the UI, the tracker checks for overlap across all active sessions in the same repository:
private getSharedFiles(state: SessionDiffState): Set<string> {
const shared = new Set<string>();
for (const file of state.touchedFiles) {
if (path.isAbsolute(file)) continue;
for (const [otherSid, otherState] of this.sessions) {
if (otherSid === state.sessionId) continue;
if (otherState.cwd !== state.cwd) continue;
if (!otherState.touchedFiles.has(file)) continue;
// Only flag if the other session has uncommitted changes
if (otherState.committed) continue;
if (otherState.committedFiles.has(file)) continue;
shared.add(file);
break;
}
}
return shared;
}
When shared files are detected, a yellow warning triangle appears on the terminal's diff badge. The tooltip lists the overlapping files. This is the signal: do not blindly git add these files.
The detection only fires when both sessions have uncommitted changes to the same file. If one session already committed, there is no conflict — the warning clears automatically.
Scoped Commits: File-Level Resolution
For the common case where agents edited different files, Callipso provides a "scoped commit" button on each terminal. When clicked, it reads the session's tracked diff and sends a command directly to Claude Code:
Commit ONLY the following files that were modified in this session.
Do NOT stage or commit any other files.
Files:
- js/domain/pitch-correction/corrector.js (+177 -0)
- js/ui/piano-roll/correction-panel.js (+332 -0)
- styles.css (+210 -0)
Claude Code receives the file list, stages only those files, and commits. This prevents Agent A from accidentally committing Agent B's files. But it does not solve the harder case: both agents edited the same file.
Hunk-Level Resolution: The grepdiff Pipeline
When the shared files warning is present, you need to stage individual hunks, not whole files. In an automated context where git add -p (interactive patch mode) is unavailable, the patchutils package provides a non-interactive alternative.
Stage by Pattern
grepdiff filters diff output by regex. Combined with git apply --cached, it stages only matching hunks:
git diff -U0 styles.css \
| grepdiff 'piano-key' --output-matching=hunk \
| git apply --cached --unidiff-zero
This extracts only the hunk containing "piano-key" from the full diff and applies it to the staging area. The working tree is untouched. Agent B's .correction-panel changes remain unstaged. Agent A can commit cleanly.
Stage by Position
When patterns do not cleanly separate the changes, select hunks by number:
# List all hunks
git diff styles.css | grep "^@@"
# @@ -877,7 +877,9 @@ ← hunk 1: piano-key fix (Agent A)
# @@ -1780,6 +1782,15 @@ ← hunk 2: correction panel (Agent B)
# @@ -2060,3 +2071,204 @@ ← hunk 3: more correction (Agent B)
# Stage only hunk 1
git diff -U0 styles.css | filterdiff --hunks=1 | git apply --cached --unidiff-zero
Manual Patch (Last Resort)
For cases where two agents' changes land in the same hunk:
git diff styles.css > /tmp/partial.patch
# Edit: delete unwanted `+` lines, change unwanted `-` to ` `, fix @@ header counts
git apply --cached /tmp/partial.patch
This is the only option when both agents edited the same region. At that point, there is a genuine conflict and no tool can split it automatically.
What Can Be Split and What Cannot
The boundary is the hunk — a contiguous block of modified lines with surrounding context. Two agents' changes are separable if and only if they land in different hunks.
| Scenario | Separable? | Method |
|---|---|---|
| Different files | Yes | Scoped commit (git add file) |
| Same file, different hunks | Yes | grepdiff or filterdiff |
| Same file, same hunk, different lines | Sometimes | Manual patch editing |
| Same file, same lines | No | Manual conflict resolution |
The shared files warning tells you which tier you are in. No warning means tier 1 — commit freely. Warning present means you need to inspect the diff to determine whether you are in tier 2, 3, or 4.
Worktrees vs. Shared Working Tree
Git worktrees remain the right choice for long-running, divergent work — feature branches that will live for hours or days. The disk and time cost is worth the guaranteed isolation.
But for quick parallel tasks on the same branch — "fix the CSS while I add the feature" — session-level conflict detection with hunk-level staging is lighter. No copies, no merge step, and conflicts surface immediately instead of at merge time when context is stale.
| Worktrees | Shared + Callipso | |
|---|---|---|
| Isolation | Full (separate copy) | Partial (shared files detected) |
| Disk cost | 2x working tree | None |
| Setup time | Seconds per agent | None |
| Conflict timing | At merge (deferred) | Real-time (immediate) |
| Commit safety | Guaranteed clean | Clean with scoped commit + hunk staging |
| Best for | Long feature branches | Quick parallel tasks, same branch |
What We Learned
The hunk is the atom of conflict. Not the file, not the line — the hunk. Two agents can safely share a file as long as their edits land in different hunks. This is a useful mental model for agent orchestration: assign agents to different regions of a file, not just different files, and you can parallelize more aggressively than branch-per-agent strategies suggest.
Detection without resolution is a dead end. A warning that says "you have a problem" without offering a way to solve it trains users to ignore it. The shared files warning is useful because it is paired with the scoped commit button and the hunk-staging escape hatch.
Commit granularity is an infrastructure problem. Git was designed for human developers who commit their own work. When multiple agents share a working tree, the commit boundary shifts from "files I changed" to "hunks I changed." The tooling needs to match.
As Andrej Karpathy put it: "The basic unit of interest is not one file but one agent." When agents become the primitive, the tooling around them — conflict detection, scoped commits, hunk-level staging — becomes infrastructure. We are building that infrastructure.