Back to Blog
engineeringmulti-agentarchitecture

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.

Callipso TeamMarch 24, 202610 min read

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:

typescript
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:

bash
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:

bash
# 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:

bash
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.

ScenarioSeparable?Method
Different filesYesScoped commit (git add file)
Same file, different hunksYesgrepdiff or filterdiff
Same file, same hunk, different linesSometimesManual patch editing
Same file, same linesNoManual 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.

WorktreesShared + Callipso
IsolationFull (separate copy)Partial (shared files detected)
Disk cost2x working treeNone
Setup timeSeconds per agentNone
Conflict timingAt merge (deferred)Real-time (immediate)
Commit safetyGuaranteed cleanClean with scoped commit + hunk staging
Best forLong feature branchesQuick 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.

Share: