Two green pull requests can make a red main
Two branches pass the same existing test, then merge cleanly into a version that fails it. Parallel agents need checks on the combined candidate.
Give two coding agents separate worktrees. Give them separate tasks. Each changes a different file, runs the existing tests, and reports success. Git merges both changes without a conflict.
The combined code can still fail the test that both agents just passed.
Both agents can report their results accurately. The test suite can contain the exact check needed to catch the bug. The mistake happens when someone treats two separate passing results as evidence about a third version of the program.
Here is a small, deliberately constructed example. Two regions reserve capacity from a shared budget of 100 units. The repository stores their reservations in different files, and an existing check adds them together.
| Version | East | West | Total | Check |
|---|---|---|---|---|
| Base | 40 | 40 | 80 | PASS |
| East branch | 60 | 40 | 100 | PASS |
| West branch | 40 | 60 | 100 | PASS |
| Combined | 60 | 60 | 120 | FAIL |
Both branches start at the base. Each raises its region's reservation to 60. Each runs exactly the same check. The files don't overlap, so Git has no conflict to ask anyone to resolve.
The reproduction script creates a temporary Git repository, makes those branches, runs the check on each, and merges them. It requires Python 3 and Git. Its output includes:
base: reserved=80, limit=100: PASS
east: reserved=100, limit=100: PASS
west: reserved=100, limit=100: PASS
merge: CLEAN (Git exited 0; no unmerged index entries)
combined: reserved=120, limit=100: FAIL
This is a synthetic branch example, with no agents or hosted pull requests involved. It demonstrates the failure mechanism. It says nothing about how often a particular agent or development team encounters it.
The test already knows what is wrong. We have to run it on the version that contains the problem.
Separate files can share a constraint
Worktrees solve a useful problem. Two workers can have separate checked-out files and indexes instead of fighting over one working directory. Git's worktree documentation distinguishes that per-worktree state from shared repository data, including most refs and, by default, configuration.
But separate checkout state does not make two tasks independent. The east task depends on the west reservation staying at 40. That dependency exists even though the east branch never edits west.json.
The diff tells you who changed a file. It takes more work to see who depended on that file staying the same. An agent told to stay inside a directory can obey that instruction perfectly while writing code that depends on a sibling's old interface.
There is a useful database analogy here. In A Critique of ANSI SQL Isolation Levels, Berenson and colleagues describe write skew: transactions read related values, update different items, and together violate a constraint that each transaction would have preserved alone. Our capacity example has that same shape. Git isn't a database transaction manager, so the analogy supplies a way to recognize the dependency, not a claim about Git's isolation level.
Software researchers understood the larger integration problem long before coding agents. Horwitz, Prins, and Reps' 1989 paper, Integrating Non-Interfering Versions of Programs, used program dependence graphs to reason about preserving behavior when combining changes. Their method addressed a restricted programming language. They asked which behaviors interfere when the versions are combined. That question still applies to agent changes.
Adding more worktrees doesn't answer it.
What the agent evidence actually says
The September 2026 STALE-bench paper, by Haocheng Xia, Eugene Wu, and Yongjoo Park, studies parallel agent development. Its mined and constructed results deserve to stay together.
From 4,262 candidate pairs drawn from 685 Django PRs, the authors validated 417 pairs. After correcting their grading, one of 834 agent runs showed a newly failing test that passed with each patch alone and failed when they were combined.
The constructed experiment used 12 Django helpers, three scripted interface changes, and three trials per combination. Tasks required the agent to call the affected helper. With GPT-5.5, 105 of 108 runs without visibility into the peer's change produced interference. A message describing the finalized change removed measured interference in 89 of 108 message-conditioned runs.
That setup deliberately creates incompatibilities. The mined sample comes from reviewed PR history and has its own selection limits. Neither estimates an ordinary agent team's failure rate. The message supplies complete information, not a protocol proven to discover what agents need to share.
There is a concrete dependency failure here to design around. Calling it "97% of agent merges fail" would misrepresent the experiment.
Yes, this is what integration CI is for
If your CI already blocks a change until the actual combined candidate passes the required checks, you have the relevant protection for this example. You do not need to rename it agent orchestration.
It would also be wrong to say ordinary pull_request CI only tests the PR's head branch. For an open, mergeable PR, GitHub's default actions/checkout follows the synthetic merge ref. A workflow can explicitly check out the head SHA instead.
The remaining question is which target it tested. East and west can each pass against the old base. Once east lands, west's previously passing candidate no longer represents the next proposed state of main. The check was accurate when it ran. Its answer belongs to a different version.
GitHub merge queues construct candidates with the latest target branch and the PRs ahead in the queue. For GitHub Actions, required workflows need the separate merge_group trigger. A workflow that only listens for pull_request events does not automatically provide the queue's required check.
GitLab merge trains make the concurrency tradeoff especially clear. With changes A, B, and C in order, they can test the target plus A, plus A+B, and plus A+B+C in parallel. If an earlier change drops out, the downstream candidates change and their pipelines restart. You can speculate in parallel as long as you stop relying on results for combinations you no longer intend to merge.
Rust's Bors design takes a simpler route for automatic builds. It merges a PR with the latest base, tests that commit, and fast-forwards the base after success. One automatic build runs at a time. The documentation explicitly identifies independently passing PRs that fail together as the problem this prevents.
These systems already construct and test the combined candidate. A workflow that merges local agent branches needs to preserve that protection. Ten agents reporting "tests pass" still leave the integration question unanswered if they tested ten separate candidates.
Make the tested version part of the handoff
An agent's completion message should let the next worker find the evidence. "All tests pass" leaves too much reconstruction to whoever merges it.
A useful handoff names the base and head revisions, the combined candidate that was tested, and the check command or workflow run. It also identifies the relevant environment inputs, such as the dependency lockfile and service fixtures. If a local check used dirty or untracked files, a commit ID alone cannot describe what ran.
That gives the coordinator a concrete procedure. Construct the next candidate from the current target and the intended changes. Run the required integration checks on it. If the target, changes, or relevant test inputs move, reconsider that result before landing anything. An existing merge queue can do much of this bookkeeping.
The final commit ID may change under squash or rebase. In that case, establish that the landed source tree matches the tested tree and account for any checks or builds that depend on commit metadata. A matching tree is useful evidence about tracked source, not a description of every input to a build.
External state needs separate attention. Two worktrees pointed at the same test database can interfere before Git merges anything. Give candidate runs separate database namespaces or reset fixtures between them. If a required check cannot run independently, coordinate that stage. There is no reason to serialize all the editing just because one integration step shares a resource.
And a queue only enforces the checks you put in it. Without the capacity check, the combined reservation of 120 could pass every configured gate. In this example the check exists, which is why choosing the right candidate is enough to catch the failure.
I want the branch checks while the work is still cheap to change. Once the coordinator proposes combining those branches, I want a result for that combination. A correct report about yesterday's base is not enough to authorize today's merge.
Before accepting an agent's "done," ask which version passed. Before advancing main, ask whether it is the version about to land.