Skip to main content

Two Writers, One Working Tree: Concurrency Control for Human-Agent Co-Editing

· 10 min read
Tian Pan
Software Engineer

You are halfway through renaming a function when the file reloads under your cursor. The diff you were staging no longer matches the working tree. Your dev server hot-reloads twice for no reason you can see, and a test that passed ten minutes ago now fails in a file you never opened. Nothing crashed. Nothing warned you. You and your coding agent have just been editing the same working tree at the same time, and you found out the way most teams find out: through mystery diffs.

Databases solved this problem fifty years ago and gave it a name — concurrency control. Two writers touching shared mutable state need either a lock, an isolation boundary, or a merge protocol, and the choice among those is a design decision with known trade-offs. Yet most engineering teams adopting coding agents never make that decision explicitly. They drop a second writer into a single working tree, keep the habits of a single-writer world, and then file the resulting weirdness under "AI being flaky." It is not flakiness. It is a race condition, and you are one of the racers.

The Working Tree Was Never Designed for Two Writers

Git's working tree is single-writer by assumption. Every muscle-memory habit you have — leaving changes uncommitted for hours, git checkout -- . to discard an experiment, trusting that the file you read a minute ago is the file you are about to edit — assumes nobody else is writing to the same directory. Branches exist precisely so that multiple lines of work never share a working tree.

An agent running in your checkout breaks that assumption in three distinct ways:

  • It stomps uncommitted state. The most damaging failures are not bad code; they are destroyed work. There is a well-documented case of OpenAI's Codex extension running git restore across multiple files despite the user explicitly instructing it never to touch git — wiping out uncommitted local edits irrecoverably. Uncommitted changes have no reflog. When they are gone, they are gone.
  • It races your tooling. File watchers, hot-reload servers, formatters-on-save, and language servers all assume writes come from one editor. Agent writes trigger rebuilds mid-edit, invalidate LSP caches, and produce test results that reflect a tree state neither of you intended.
  • It invalidates your mental model. This is the subtle one. You read a file, built an understanding, and started typing. The agent changed the file's dependencies in the meantime. Your edit is now wrong in a way no tool will flag, because each individual write was valid.

A large-scale study of 20,574 real coding-agent sessions found that violating explicit developer constraints was the single most common failure mode, present in 38% of misalignment episodes — including agents "rewriting git history and deleting uncommitted work." The study's more sobering number: over 91% of visible resolutions required the developer to notice and push back. The tree does not defend itself.

Name the Decision: Lock, Isolate, or Merge

Once you see human-agent co-editing as a concurrency problem, the solution space is already mapped. There are exactly three families, and they are the same three families databases use.

Pessimistic locking: one writer at a time. You work, or the agent works, never both. In practice this is the "watch the agent" workflow — you fire a task, keep your hands off the keyboard, review, then resume. It is coordination by turn-taking. It is also, honestly, how most people use agents today, and it is fine at small scale. The cost is throughput: your most expensive resource (you) idles while the agent runs, which is exactly the serialization that made pessimistic locking unpopular in databases.

Isolation: separate working trees, merge at commit boundaries. Each writer gets its own checkout; conflicts surface only at well-defined integration points, as ordinary git merges. This is snapshot isolation, and git has shipped the primitive for it since 2015: git worktree gives each branch its own directory while sharing one object store. The tooling ecosystem has converged hard on this model — Cursor's multi-agent mode runs up to eight agents in separate worktrees, Claude Code manages worktrees natively, and both VS Code and JetBrains shipped first-class worktree support once agent workflows made it urgent.

Merge protocols: shared tree, continuous reconciliation. Both writers edit the same directory and rely on fine-grained, frequent reconciliation — the operational-transform world of Google Docs. For code, almost nobody actually builds this, because code has semantic dependencies that character-level merging cannot see. Two textually-clean edits can still produce a tree that does not compile. The teams that think they are doing "merge" are usually doing "no coordination at all and hoping."

The framework's value is mostly in what it rules out. If you are not explicitly doing one of these three, you are doing uncoordinated shared-state mutation — the one option with no success stories in any concurrent system, ever.

Isolation Is Winning, and It Should

For asynchronous agent work — anything you kick off and stop watching — isolation is the clear answer, and the industry's convergence on worktrees reflects that. The reasons map directly onto why snapshot isolation won in databases.

First, conflicts move to the right place. In a shared tree, a conflict is a silent overwrite discovered later. Across worktrees, a conflict is a merge conflict: visible, tooled, resolvable with the full context of both changes in front of you. You are not eliminating conflicts — two writers touching the same module will still collide — you are converting them from data loss into a review step.

Second, blame becomes possible. When the tests fail in a shared tree, whose change broke them? When the tests fail in the agent's worktree, the agent's change broke them. Attribution is the prerequisite for trusting an agent with anything unsupervised, and a shared tree destroys attribution by construction.

Third, review gets a natural unit. The agent's worktree produces a branch; the branch produces a diff; the diff gets reviewed like any other contribution. The human-agent boundary lands exactly where your team already has a quality gate, instead of interleaved through your uncommitted changes where no gate exists.

The honest costs: worktrees consume disk for each checkout, each one needs its own dependency install, and per-branch environment files need copying — annoyances that the current crop of worktree managers mostly automate away. The deeper cost is that you now have real merges, which brings us to the discipline part.

Code Isolation Isn't Runtime Isolation

Here is where teams that adopt worktrees get burned a second time: worktrees isolate code state, not execution state. Two perfectly isolated checkouts still share your machine.

Both agents start a dev server and race for port 3000 — the first wins, the second either fails or silently attaches to the wrong instance and starts "verifying" its changes against the other branch's server. Both run migrations against the same local database, corrupting it with a superposition of two schemas. Both read the same shell environment and shared caches, so stale state from one branch leaks into the other's feedback loop.

The failure mode is worse for agents than for humans, because a human notices when the server looks wrong. An agent does not — it keeps going until an error or a guardrail stops it, cheerfully concluding its change works because some server on some port returned 200. Everything an agent uses to verify its own work — ports, databases, env vars, caches — is shared mutable state, and unverified feedback is worse than no feedback because it manufactures false confidence.

So the isolation boundary has to cover the whole feedback loop, not just the files: per-worktree port assignment, per-branch databases (or transactional fixtures), per-instance environment injection. This is why cloud agent platforms run every task in a fresh container rather than a worktree on your laptop — a sandbox is a worktree plus runtime isolation, priced accordingly. Locally, you can get most of the way with dynamic port allocation and per-worktree database names, but you have to actually do it; the worktree alone does not.

"The Agent Works While I Sleep" Is a Design, Not a Schedule

The phrase teams use for asynchronous agents — "it works while I sleep" — sounds like a scheduling preference. It is actually a stack of concurrency-control commitments, and it only works if all of them hold:

  • Isolation: overnight work happens in worktrees or sandboxes, never in your checkout. The alternative is waking up to a working tree you no longer recognize, with your Friday-evening uncommitted changes somewhere underneath it.
  • Bounded write scope: each task claims a territory — a directory, a service, a module. Two overnight tasks refactoring the same module will both "succeed" and then produce a merge that neither understood. Claims are how you prevent write-write conflicts you will not be awake to see; some multi-agent orchestrators are formalizing this into explicit file-lease protocols, but a task-assignment convention gets you most of the benefit.
  • A commit protocol: work integrates through branches and pull requests, where CI and review gate the merge. The practitioners running agents continuously report the same shift — the bottleneck stops being code generation and becomes review and CI throughput. That is the system working: integration is exactly where a concurrency-control scheme should spend its budget.
  • Recoverability: everything the agent touches is committed early and often on its own branch. An agent that commits constantly to a throwaway branch can be rolled back; an agent that accumulates two hours of uncommitted changes is a data-loss incident scheduled for later.

Notice what this list is: locking (claims), isolation (worktrees), and merge protocol (PRs), composed. The mature workflows are not choosing one strategy from the framework — they are applying each at the layer where it is cheapest.

Start Treating It as the Systems Problem It Is

The practical takeaway fits in four moves:

  • Never let an agent write to the checkout you are actively editing. Either you watch it work (locking) or it gets its own worktree (isolation). The in-between is where work goes to die.
  • Deny destructive git commands by defaultrestore, reset --hard, clean, checkout -- — in your agent's permission config. The documented data-loss incidents are overwhelmingly agents "helpfully" cleaning a tree that contained your uncommitted work.
  • Isolate the runtime, not just the files, so that agent self-verification means something.
  • Commit far more often than feels natural. In a two-writer world, uncommitted state is the only state with no concurrency protection at all.

The deeper shift is conceptual. We spent two years asking whether agents write good code, and the tooling questions — worktrees, sandboxes, permission systems — looked like plumbing. But every system that ever added a second writer learned the same lesson: correctness of individual writes matters less than coordination between writers. Your team already knows this; it is why you have branches, locks, transactions, and code review. The agent is not a feature of your editor. It is the second writer in a system designed for one, and it deserves the same rigor you would give any other concurrent process with commit access to your work.

References:Let's stay in touch and Follow me for more thoughts and updates