On 2026-10-13 Anthropic published Redesigning source control at Anthropic. It explains why the company moved its monorepo from git to Scion, a version control system it built in-house. The headline number is the merge rate:

In May 2026, Anthropic averaged around 2,500 code changes merged per day; as of September, we merged over 30,000 per day.

Most of us are nowhere near that. The post says so itself: “Scion is by no means a suitable replacement for git for general use.” But it also names the specific git workarounds Anthropic used and outgrew: shallow clones, sparse checkouts, promisor remotes (partial clone), mirrors and repository snapshots. Those are tools a normal team reaches for once agents start cloning and pushing all day. So I measured them on one machine, at sizes a normal team could actually reach. I wanted to know which one bends first, and how badly.

The short version:

  1. Pushing to one branch is the first wall, and it grows with the square of the agent count. When 32 agents pushed to main at once, exactly one landed. With a pull --rebase retry loop, all 32 got in after 519 to 528 push attempts.
  2. git status was not the problem. At a million files it took 0.91 s warm, and 0.29 s with -uno. The built-in file system monitor that would speed it up refused to start on Linux.
  3. A blob:none partial clone with a full checkout was slower than a plain clone. It took 11.4 s against 2.7 s at 100,000 files and 92.6 s against 9.3 s at 300,000. At a million files it hadn’t finished after 900 s, where the plain clone took 29.9 s.
  4. A partial clone combined with a sparse checkout was fast. One directory out of 100 at a million files took 3.3 s to clone.
  5. History commands on partial clones turn into round trips. git log -p -n 100 made 100 fetches on a blob:none clone. On a tree:0 clone, git log -- d000 made 500 fetches and took 110 s, against 0.008 s on a full clone.

What Anthropic reports, and what I can check

Everything in this section comes from Anthropic’s post. I can’t verify any of it. These are the claims that matter for this article, quoted or close to it:

Claim (Anthropic’s post, 2026-10-13)Status
~2,500 merges/day in May 2026, over 30,000/day in SeptemberReported by Anthropic, not verifiable
Main branch history “almost 100 GB”, compressed working copy “over 25 GB”Reported, not verifiable
“on the order of 25 million code checkouts per day for CI”Reported, not verifiable
“at our peak, we operated several hundred internal git mirrors”Reported, not verifiable
Partial clones needed “more and more care” to keep “diff and blame, working”Reported. I measured the general mechanism below.
In Scion “every commit, rebase, or merge happens on the server” and refs are discouraged except main and release branchesDesign description
“There is no equivalent of packfiles … so no repository maintenance is needed”Design description
Infrastructure “200x smaller” than the git mirrors; CI got “a 25% CPU capacity win”Reported, not verifiable
Built by “a team of seven people” in five monthsReported, not verifiable

Nothing below measures Scion. What I can measure is the git side of the story: the limits the post says pushed Anthropic away, at a much smaller scale.

Setup

All of it ran on one Linux machine (8 vCPUs, 15 GB of RAM, a local ext4 disk) with git 2.47.3. Every clone used file:// with --no-local, so git used its real pack transfer instead of copying files. There was no network and no hosted forge.

The repositories are synthetic and built with git fast-import. Each has 100 top-level directories of small unique text files, at 10,000, 100,000, 300,000 and 1,000,000 files. Each also has 500 commits that rewrite 20 random files each. That is a short history for a big tree, the opposite of Anthropic’s 100 GB of history over 25 GB of working copy. Keep that in mind for the clone numbers.

The code and raw output are in yopa-experiments at 53b174c. Every table here comes from results/raw.log through summarize.py.

Cliff 1: everyone pushes to main

This is the one I expect an agent-heavy team to hit first, long before repo size matters. Each of K clones of a 10,000-file repo committed one new file, all in different directories, so there were no content conflicts. Then they all pushed to main at the same moment. I ran three trials per K.

AgentsLanded on the first tryPush attempts until all landed (3 trials)Most attempts by one agentRetry time (median)
111, 1, 110.00 s
213, 3, 320.23 s
4110, 10, 940.43 s
8136, 36, 3581.16 s
161135, 135, 136162.97 s
321528, 519, 5253210.16 s

In every trial exactly one push won. The others got this:

1
2
remote: error: cannot lock ref 'refs/heads/main': is at 91d2bd2d... but expected c817c4f6...
 ! [remote rejected]   HEAD -> main (failed to update ref)

The retry loop is git pull --rebase followed by git push, repeated until the push succeeds. Each round lets one more agent in, so the total is close to K(K+1)/2. For K = 32 that is 528, which one trial hit exactly. The last agent needed 32 attempts. Ten seconds doesn’t sound like much, but the setup was favorable. Client and server were on the same disk with no network latency, every commit touched a different file, and nothing ran CI between attempts. Over a real network, each round trip makes the race window wider.

This is where Anthropic’s design differs most directly from git. In Scion, commits and merges happen on the server and are cheap, and moving a ref is described as “a compare-and-swap on a single Spanner row”. On git, the usual way out is a merge queue or bot that serializes landing. The post also says they “haven’t been able to effectively replicate the industry standard of a global repository lock on the merge queue” and moved to post-merge testing instead. I didn’t test a merge queue here.

If your agents push straight to one branch, count your push attempts. If the attempts grow much faster than the commits, you have hit this cliff.

Cliff 2: a big working tree (smaller than I expected)

FilesClone + checkoutIndexstatusstatus -unostatus with untracked cachesparse-checkout set to 1 of 100 dirsstatus after sparse
10,0000.63 s1.0 MB0.036 s0.016 s0.026 s0.21 s0.005 s
100,0002.70 s8.9 MB0.088 s0.034 s0.038 s1.52 s0.061 s
300,0007.97 s26.5 MB0.206 s0.086 s0.096 s4.87 s0.172 s
1,000,00030.16 s88.2 MB0.908 s0.285 s0.308 s17.12 s0.187 s

These are warm-cache medians of five runs. I didn’t drop the page cache on a machine other agents share, so a cold status would be slower. At a million files, status grows to about a second, and the untracked cache brings it down to about a third of a second. That is slow for an interactive prompt, but it won’t stop anyone.

The tool that would help most on a huge tree is git’s built-in file system monitor. On this machine it didn’t start:

1
2
$ git fsmonitor--daemon start
fatal: fsmonitor--daemon not supported on this platform

That matches git’s build configuration at v2.47.3. config.mak.uname sets FSMONITOR_DAEMON_BACKEND only for darwin and win32. Many agent sandboxes are Linux containers, and there this speed-up isn’t available.

Two numbers surprised me. First, narrowing a million-file checkout to one directory took 17 s, about half as long as the clone itself. Second, the index stayed at full size after the sparse checkout. I didn’t enable index.sparse, which is meant for exactly that.

Cliff 3: partial clone with a full checkout

RepoVariantClone.git sizeFiles on disk
100kfull2.69 s31.4 MB100,000
100k--depth 12.55 s27.8 MB100,000
100k--filter=blob:none11.42 s38.0 MB100,000
100k--filter=tree:011.13 s36.1 MB100,000
100k--filter=blob:none --sparse, then 1 dir0.49 s + 0.17 s14.2 MB1,000
300kfull9.32 s85.4 MB300,000
300k--filter=blob:none92.55 s108.4 MB300,000
1Mfull29.88 s274.3 MB1,000,000
1M--depth 129.04 s270.7 MB1,000,000
1M--filter=blob:nonenot done after 900 s
1M--filter=blob:none --sparse, then 1 dir3.31 s + 1.96 s117.0 MB10,000

A partial clone downloads commits and trees, and then checkout fetches the blobs it needs. Git’s partial clone notes say checkout does this in one batch:

checkout (and any other command using unpack-trees) has been taught to bulk pre-fetch all required missing blobs in a single batch.

It was still one batch here, but its cost didn’t grow in a straight line. Three times the files took eight times as long (11.4 s to 92.6 s). At a million files I killed the first attempt by hand at 798 s and the rerun hit its 900 s timeout. In both cases the git fetch --stdin child was at about 100% CPU on the client. I didn’t profile it, so I can’t say which part of the client is slow. It may also behave differently on a newer git or over HTTPS.

Part of the gap is my synthetic repo. Its history is small, so a partial clone has little to skip. On a history-heavy repo like the one Anthropic describes, blob:none saves much more. The useful result is the last row of each size. A partial clone combined with a sparse checkout is the variant that stayed fast, because it never asks for most of the blobs. That 117 MB .git at a million files is probably mostly the index, which keeps every entry without index.sparse (it was 88 MB in the full checkout). I didn’t measure the index in that clone separately.

Cliff 4: history questions on a partial clone

Anthropic writes that keeping “operations that depend on history, like diff and blame” working on partial clones took more and more care. Git’s own notes describe why:

Dynamic object fetching invokes fetch-pack once for each item because most algorithms stumble upon a missing object and need to have it resolved before continuing their work.

I counted those fetches with GIT_TRACE2_EVENT on clones of the 100,000-file repo:

CloneCommandTimeLazy fetches
fullgit log -p -n 1000.068 s0
blob:nonegit log -p -n 1001.801 s100
tree:0git log -p -n 10029.349 s200
fullgit log --oneline -- d0000.008 s0
blob:nonegit log --oneline -- d0000.007 s0
tree:0git log --oneline -- d000109.954 s500
fullgit blame d001/s22/f0017201.txt0.027 s0
blob:nonegit blame d001/s22/f0017201.txt0.065 s1
tree:0git blame d001/s22/f0017201.txt17.017 s63

That is one fetch per commit for log -p on blob:none, and about one per commit in the 501-commit history for a path-limited log on tree:0. The blame target is the most-changed file in the repo, but it changed only 4 times, so blame got off lightly. My first pass undercounted these numbers. Auto-gc merged the new packs once there were more than 50, so I reran with gc.auto=0. Both passes are kept in the raw log.

This is the job Scion moves to the server. The post says jobs “ask the server” for diffs and history instead of keeping a local repo. On git, the closest thing is a forge API or a full clone kept on a CI cache volume. In practice that means either give history-heavy jobs a full clone, or let them ask an API, and use tree:0 only for jobs that never look back.

Not a cliff here: repacking

On the million-file bare repo, git repack -a -d -f took 13.27 s and git gc took 5.61 s. At this size that is routine maintenance. Anthropic’s point is that it doesn’t scale with their history and mirror count (“no repository maintenance is needed” in Scion). Nothing I built is big enough to show that.

What I did not measure

  • Scion, and any of Anthropic’s numbers. They are all self-reported.
  • A real network or forge. Everything ran over file:// on one disk. Network latency would make the push race and the per-object lazy fetches worse, but that is an expectation, not a measurement.
  • A history-heavy repo. Mine has 500 commits. The clone comparisons would favor --depth 1 and blob:none much more on a repo with long history.
  • A cold cache, index.sparse, Scalar, git maintenance schedules, or a newer git than 2.47.3.
  • A merge queue or server-side merge as the fix for cliff 1.
  • Why the million-file blob:none checkout was slow. I only measured that it was.

What to do

  • Don’t let many agents push to one branch directly. Give each agent its own branch or worktree (see Give Every Agent Its Own Worktree) and land through something that serializes merges. Watch the number of push attempts per landed commit.
  • Use partial clone together with sparse checkout, not partial clone alone. On my repos, blob:none with a full checkout was the slowest option, and blob:none --sparse was the fastest. For reading part of a repo from a short-lived job, the Lambda sparse checkout post uses the same idea.
  • Keep history questions off tree:0 clones. Give those jobs a full or blob:none clone, or answer them through an API.
  • Don’t plan on fsmonitor in Linux containers with git 2.47. Use core.untrackedCache and status -uno where they fit.
  • Measure before you migrate. Every number here came from a script that runs in minutes on one machine. Run it at your own file count and agent count before deciding git is the problem.

References