← writing

Why task files conflict when two agents edit them

The usual answer is git worktrees. We replayed 8,396 merges to find how often it actually happens, which type it is, and what worktrees defer rather than fix.

Ask why two agents conflict on a task file and the first page of results agrees: use git worktrees. Give each agent its own working directory and they stop overwriting each other.

That advice is right, and several of the pages giving it are honest enough to add the qualifier in the same breath — worktrees isolate the working copy, not the merge. Two branches that changed the same line still meet at merge time.

So the question underneath is the interesting one: what is it about a task file that puts two agents on the same line in the first place, and how often does it actually happen? The second half has an answer we measured, and it is smaller than the framing on those pages implies.

The mechanism is the file's shape, not the agent

Git merges text. It does not know what a task is. When two branches change the same line of the same file and the common ancestor had a third value, git stops and asks.

A task file in the common format stores state as a mutable field:

---
id: TASK-31
title: Fix web-ui build errors
status: In Progress
---

Moving a task is rewriting that line in place. Two agents working two tasks never collide. Two agents that both touch one task — one finishes it, one adds a note and updates the timestamp — write different text to the same line, and git has no rule for which one wins.

Here is that exact merge, from a real repository, replayed today:

---
id: TASK-31
title: Fix web-ui build errors
<<<<<<< HEAD
status: Done
updated_date: '2026-02-18'
=======
status: In Progress
updated_date: '2026-02-18 04:47'
>>>>>>> branch

Nothing about that is agent-specific. It is what a mutable field in a shared file has always done. Agents matter only because they raised the number of branches touching one task per week.

How often, measured

We stopped guessing and counted. Tasks stored in git leave their merge history behind, so every merge can be replayed and git asked directly whether it conflicts.

The method was git merge-tree --write-tree against a merge commit's two parents. That is not a heuristic reading of a diff — it is the same machinery a real merge runs.

Across 8,396 merge commits in 130 public repositories using file-based trackers:

Merges that conflicted somewhere681 (8.1%)
Merges that conflicted in a task file44 (0.52%)
Repositories that hit it at least once20 of 130
Repositories that never hit it110

One merge in roughly two hundred. A team doing twenty merges a month meets this about once every five months. If you came here from a page describing it as a constant tax, that is not what the history says, and we would rather write the number than the adjective.

The average hides the shape

The distribution is not a rare event spread thinly. It is concentrated: 110 of the 130 repositories never produced one, and a handful produce them steadily. The worst in the sample recorded seven.

That repository is public, so the count can be re-run rather than trusted. On 2026-09-25, against a fresh clone of usmcamp0811/crystal-forge:

git clone --filter=blob:none --no-checkout \
  https://github.com/usmcamp0811/crystal-forge.git && cd crystal-forge

while read -r merge p1 p2 p3; do
  [ -n "$p3" ] && continue                      # skip octopus merges
  out=$(git merge-tree --write-tree --name-only "$p1" "$p2" 2>/dev/null) && continue
  grep -qE '^backlog/tasks/' <<<"$out" && echo "$merge"
done < <(git rev-list --merges --parents HEAD)

127 two-parent merges, nine conflicted anywhere, seven under backlog/tasks/. The same seven the probe recorded three weeks earlier. Swap the path prefix for wherever your own tasks live and the command answers the question for your repository instead of ours.

Two different conflicts wear the same word

Classifying 28 conflicts from the six most active repositories in the sample split them cleanly, and the split decides what can be done about them:

TypeShareWhat happened
CONFLICT (content)89%Two branches edited the same task
CONFLICT (add/add)7%Two branches created a task with the same ID
CONFLICT (modify/delete)4%One branch edited, the other deleted

The crystal-forge re-run landed in the same proportions without being asked to: six of its seven were content conflicts, and the seventh was an add/add on task-57.

Content conflicts are a storage-format problem. They exist because state is a field that gets rewritten. Store state as appended events instead — one file per event, nothing edited in place — and two branches write two different files. There is nothing for git to reconcile. Our own merge suite asserts 0 conflicts when three people move one task on three branches and the results are merged in every order.

Add/add conflicts are not, and this is the part worth being blunt about. They are an identifier collision: two branches, diverged from one ancestor, both allocate TASK-57. Putting tasks in separate files does not help — it makes the failure quieter, because two tasks with one number merge without complaint and nobody is told.

Our own specification had that hole. It said identifiers were derived from the journal, which is true and insufficient: two branches read the same journal and derive the same number. The fix was to make the primary identifier a ULID and the human-readable KAD-42 a label assigned at fold time, in ULID order, so the two branches produce different numbers automatically. That change was made to the spec before the code, because the probe found it before a user did.

What worktrees are still for

Nothing above argues against them. Two agents editing the same source tree in place is a much worse problem than a task file conflict, and it happens far more often than one merge in two hundred. Worktrees fix that, and they are the right answer to the question most of those nine pages were actually asked.

They just do not touch this. A worktree changes where the edit happens; the merge still compares two versions of one line. The only thing that removes a content conflict is not having a line that both sides rewrite.

What the numbers do not cover

Worth stating, because the limits are part of the measurement:

  • Only explicit merge commits are visible. Work landed by rebase or squash merge leaves no two-parent commit to replay, so the true rate is a floor, not an estimate
  • At most 300 merges were replayed per repository, which understates the busiest projects
  • The type classification came from 6 of the 20 affected repositories, not all of them
  • Public repositories only. Private team repositories, where parallel work is more common, are not in the sample and could look different

The honest summary: this is rare in most repositories, regular in a few, and when it happens it is almost always the one type a change of file shape removes outright.


kadence stores tasks as an append-only journal for this reason. The merge test is the assertion behind the zero above, and the probe is where the counts on this page come from — each one names the test that keeps it true.

Where the numbers come from

  • Probe run 2026-09-25, nine results: `why do task files conflict when two agents edit them in git` returned mindstudio.ai, jpaul.me, two dev.to posts by the same author, augmentcode.com, arbitlab.com, alook.ai, termdock.com and shopclawmart.com — nine written pages and no repository
  • 8,396 merge commits across 130 public repositories using file-based trackers — kadence docs/research/probe-a-results.md
  • The search began with 245 candidate repositories found by code search for `backlog/tasks/` or `.issues/`; 42 had no task files and 73 no merge commits, leaving 130 — kadence docs/research/probe-a-results.md
  • Of 8,396 replayed merges, 681 (8.1%) conflicted somewhere and 44 (0.52%) conflicted in a task file; 20 of 130 repositories hit it at least once and 110 never did — kadence docs/research/probe-a-results.md
  • Classification of 28 conflicts from the six most active repositories: 25 are CONFLICT (content); 2 are CONFLICT (add/add) and that is 7% of them; 1 is CONFLICT (modify/delete) and that is 4% of them — kadence docs/research/probe-a-results.md
  • The probe replayed at most 300 merge commits per repository, counts explicit merge commits only, and classified types on 6 of the 20 affected repositories — stated limitations, kadence docs/research/probe-a-results.md
  • Reproduction run 2026-09-25 against a public clone of usmcamp0811/crystal-forge: 127 two-parent merges replayed with `git merge-tree --write-tree`, 9 conflicted anywhere, 7 conflicted under backlog/tasks/ — six reported by git as CONFLICT (content) and one as CONFLICT (add/add). The conflicted region of task-31 is the frontmatter `status:` line, Done against In Progress
  • The primary task identifier is a ULID and the human-readable label is derived at fold time, so two branches that create a task independently get different numbers after a merge — SPEC.md module M5 and the integration test `three branches create tasks independently` in test/integration/merge.test.ts, kadence repository at 0.6.0