Skip to content
All articles
  • AI code review
  • GitHub Actions
  • Architecture

Monorepo vs. multiple repos: the AI review tax

A small change, long Jira ticket, and an AI reviewer that keeps reading. Where the tokens go, why more context makes the review worse rather than better, and why I would give independent services their own repos.

Eduard Smirnov13 min read

Say you are changing a checkout validation rule. Got a small pr. A GitHub Action picks it up, sends the diff and the Jira ticket to your AI reviewer, and asks it to check for regressions and if all implemented according to the Jira ticket. Except the ticket also mentions payments, account settings, and a migration somebody discussed six months ago. A few tool calls later, the reviewer is reading half the workspace to answer a question about an empty field.

You push your fix. The Action runs again. The postcode now has a research budget.

This is the bit of the monorepo debate that bothers me. You keep paying for the review to work out which parts of the system matter. And somebody on the team has to look after the rules that are supposed to stop it doing quite so much of that.

I used to think this was mostly about the bill. But the more I read about how models behave with long inputs, the less sure I am that we're even getting a better review for the money. We may be paying more to make it worse. Which is a fairly annoying deal.

So if the services have their own owners and release schedules, I would put them in separate repos. Same review workflow, smaller place to look. I want a good reason to take on the extra machinery a monorepo needs here, and "the AI can figure it out" doesn't really do it for me.

How a ticket turns into a repository tour

It usually starts with something reasonable enough:

Review the checkout changes against the Jira ticket. Check the existing architecture and make sure nothing else breaks.

It sounds sensible until you try to define "nothing else." Checkout? Every caller of its payment client? The abandoned account-settings redesign still linked from the epic?

A Jira ticket might have the actual requirement, three rejected approaches, an old argument, and a suggestion for next quarter. All in the same description, occasionally distinguished by bold text. Someone on the team probably remembers which bits still count. The reviewer gets the whole lot and has to work it out.

If the Action fetches linked tickets and lets the agent search the whole monorepo, there's plenty to get distracted by. Especially with "make sure nothing else breaks" hanging over it. I can see why it keeps looking. We did ask.

This isn't a hypothetical I made up for the post. The person behind code-review-graph describes watching Claude Code review a PR in the Next.js repo (roughly 28,000 files): it doesn't know which related files matter, so it reads everything it can find, scanning hundreds of files when about fifteen were relevant. His fix was to build a whole Tree-sitter dependency graph and serve it to the agent over MCP so it would stop wandering. I will come back to that, because it's the shape of every monorepo fix I have seen.

Neither Jira nor Git automatically triggers a full-repository review. Your integration decides what gets fetched and which tools the reviewer can use. But "here is everything, please be thorough" is a tempting first implementation. Especially when the demo only has to survive one PR.

Then you leave it running. GitHub Actions can trigger when a PR opens and again when you update its head branch through synchronize. So that little investigation may start over after every push. GitHub's event reference has the details.

More context is not free, and it's not neutral either

I get the appeal of giving it the whole repo. The context window is big enough, so surely it can ignore the stuff it doesn't need?

That's what I assumed too. The research made me a lot less comfortable with it.

A paper at EMNLP 2025, Context Length Alone Hurts LLM Performance Despite Perfect Retrieval, tested five models on maths, QA and coding tasks. Even when the model could find every relevant piece of information, accuracy dropped by somewhere between 14% and 85% as the input got longer, well inside the advertised context limits.

The odd bit is that the drop was still there when they replaced the irrelevant text with whitespace. Even masking it out, so the model could only attend to the relevant tokens, didn't get rid of it. In those tests, length itself was a problem. So I am wary of "it'll just skip the bits that don't matter."

Chroma's Context Rot report ran 18 models (GPT-4.1, Claude 4, Gemini 2.5, Qwen3 among them) through controlled experiments and found reliability falling as input grew. Some of the tasks were as simple as retrieval and copying text.

They also found that similar-looking distractors hurt more. And a monorepo has plenty of those: another team's validator, a different service's payment client, the old version of the thing you're changing that's somehow still checked in under a different path.

Then there's Adobe's NoLiMa benchmark. Twelve models claiming 128K or more of context; at 32K tokens, ten of them fell below half their short-context score. You can get to 32K pretty quickly in a review. A diff, a handful of related files, one generous Jira epic with its comment history.

I haven't seen a controlled study that takes the same code change and compares review quality in a monorepo and split repos. These papers don't settle that question. But I keep seeing the same general problem across different groups, and I don't have a good reason to assume code review gets a pass. Hand me forty files for a one-line change and I would worry about missing the important bit. I worry about the model doing that too.

The tax shows up more than once

A big repo can still have a cheap review. Cloning code, searching filenames, and actually sending files to a model are different things. The bill depends on what the reviewer does and how the provider charges for it. Git doesn't charge you per folder.

What gets expensive is sending the same unnecessary stuff over and over. Architecture docs, unrelated packages, long ticket histories. Then another pass because the first one didn't finish. Then your next push, and everyone else's PRs doing the same thing.

Take some made-up round numbers: six runs at 80,000 input tokens each process 480,000 tokens. Six at 15,000 process 90,000. That's arithmetic, not a savings benchmark; caching, output tokens, and the provider's pricing affect the bill. It does show why "a bit more context" stops being a small decision when the workflow runs all day.

Some of the reported numbers are worse. Augment Code tested ten open-source review tools against a 450,000-file monorepo and reported users hitting 784,000 tokens on a single review against a 128,000 limit, and half a million tokens on a small merge request, because the tool concatenated every changed file's diff and then kept reading.

What stuck with me was their report that none of the tools caught cross-service breaking changes. After all that reading. The monorepo paid the context tax and still didn't get the cross-service safety you'd hope for.

Then there are the comments. Somebody has to read the bot's concerns about an unrelated package and decide whether any of them matter. Enough of that, and people start skimming. The useful finding gets the same attention as the previous nine architectural suggestions nobody asked for.

You've bought an automated reviewer and acquired another inbox.

What separate repos buy you

Put checkout in its own repo and there's a lot less to explain. The README and tests are about checkout. You don't need a paragraph telling the reviewer which of twelve teams' conventions apply to a postcode validator.

Start the Action with that service's diff, local code, and tests. If the change touches a payment request, give it the API contract and version. If that leads to another dependency, follow it. There's still room to investigate; mentioning the account-settings redesign just doesn't automatically put its entire implementation in front of the reviewer.

The permissions are easier to reason about too. The standard Actions GITHUB_TOKEN belongs to the repository containing the workflow, as GitHub documents. In a monorepo, that still means the whole repo, not one directory. Separate private repos give you a smaller scope with that token. Unless, of course, you hand the job broader credentials or clone all the other repos next to it. Public code is still accessible either way.

GitHub's own engineering blog published a checklist for reviewing agent workflows in May. First up: prompt injection in CI. An agent reads a PR body, an issue, or a commit message, that text ends up in the prompt, and the whole thing runs with the token's permissions. A Jira ticket fits right in there. Feed it more of the epic and you're feeding it more untrusted text.

Read-only helps, but reading is also what we're worried about. An agent nudged into "helpfully" gathering more context can use that token to read every service in the monorepo. With a repo for one service, that same token takes it a lot less far.

This is why I keep coming back to separate repos for services that ship independently. The team already knows where the service starts and ends. Let the reviewer start there too. You still need a decent workflow, just less explanation of where it is and what it's allowed to look at.

If billing and checkout already have separate deployments and owners, keeping their source in one checkout doesn't remove that separation. It gives the review automation another boundary to reconstruct.

The part that gets worse next year

A monorepo review can start with a neat path map. Changes under apps/checkout go to the checkout reviewer. Easy.

Then checkout starts using a shared payment package. Somebody moves a directory. A root config turns out to affect three apps, and there's a runtime dependency the project graph doesn't know about. None of this is unusual. But somebody has to keep the review routing caught up, or it starts quietly missing things.

You can build good tooling for this. You also have to maintain it. The repo keeps changing after the person who wrote the first version moves on to something else.

Even people arguing for monorepos with AI keep coming back to this. Graphite's guide to AI review in monorepos recommends CODEOWNERS and path-based rules to keep the reviewer's responsibilities clear. The code-review-graph project exists because the agent needs a dependency graph to stop reading everything.

And this write-up from someone who started the year convinced monorepos were the obvious choice for agents lands in a similar place: broad search still needs package, ownership and domain boundaries. Better models may follow the map with less help. Somebody still has to maintain the map.

Fair enough. I just look at that work and wonder how much of it I would need if the services were in their own repos to begin with.

GitHub Actions path filters only solve part of the problem: they decide whether a workflow starts. They don't stop a running agent from reading other directories. And if a required workflow is skipped by a path filter, its check can remain pending and block the merge. That behavior is documented in GitHub's required-check troubleshooting guide. The two-line fix now needs someone who remembers how the review router works.

Shared changes make the tradeoff harder. A schema change may actually need several consumer reviews. Let every job gather the same broad context and you duplicate work. Clamp the context too aggressively and you risk missing the dependency that mattered. A review that runs out of room and still reports "looks good" is worse than one that admits it couldn't finish.

Most of this won't look like a big failure when it happens. You'll add an exception to the config and get on with your day. Rerun the job. Dismiss a comment. It's easy to lose track of how much time is going into it.

What I would actually put in GitHub Actions

I would keep one reusable workflow and have each service call a pinned version. I am not volunteering to maintain twelve slightly different copies of the same YAML. Try workflow and prompt changes on a few repos, see what happens, then roll them out to the others.

I would have the job start with the PR's actual base and head commits and the complete list of changed files. From there, read the diff and the local dependencies it touches. For Jira, give it the current acceptance criteria and the decisions that still apply. Keep the original ticket available if it needs to check something. No need to feed it the entire family tree of the epic up front.

For the checkout example, the brief could be:

Reject an empty billing postcode before payment submission.
Preserve checkout with an existing saved billing address.
Check the changed validator, its tests, and the payment schema.
The account-settings redesign in the epic is future work.

I would start there. If it needs another package or contract, fine, but I would want to know what in the change sent it looking.

Also, stop reviewing old commits once a new one arrives. This bit handles the triggers and cancellation; your actual review job goes below it:

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

concurrency:
  group: ai-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true

Actions concurrency can cancel the older run, though you won't get a refund for a model call you've already sent. I would also check the PR head again before posting, in case it changed during the review. And update the existing summary. We have enough essays in the PR already.

I would put hard limits in the runner for total model usage, tool calls, and how much text it retrieves. A timeout helps too, but five minutes is quite enough time to spend money. If the budget runs out, say what didn't get reviewed. You can ask it nicely to use fewer tokens as well. I just wouldn't rely on that.

The permissions are one part I would be fussy about. Keep the analysis read-only; let a separate job have permission to post the comment. Run tests without model credentials. Fork PRs need some care here. Switching to pull_request_target and running untrusted PR code with secrets available is a dangerous shortcut, as GitHub's security guidance explains.

I wouldn't make the bot a merge gate on day one. Let it earn people's attention first, alongside the normal tests and human review. If the model is down or couldn't finish, show that. Then decide whether it's a good enough reason to hold up everyone else's work.

Yes, separate repos have a bill too

You have dependency versions to manage. A change across services may need linked PRs and a staged rollout. Consumer compatibility needs tests, not an optimistic comment saying the other team will update their side soon.

I do see the appeal on the other side. Nx argues that seeing the whole system helps an agent make consistent changes and catch downstream impact, and that repo boundaries wipe its context. For an agent writing a change across several services, I think there's something to that. For one reviewing a postcode validator, all that context is exactly what I am worried about. Different jobs.

And if the services deploy independently, you've already got different versions running in production. Keeping the source together doesn't change that. You'll need versioned contracts and compatibility checks either way.

Splitting tightly coupled code can make everything worse. If nearly every feature requires coordinated edits across three repos, take the hint. The boundaries may be wrong. I wouldn't create a repository for every utility function and then celebrate how small the AI prompts became.

I would keep a monorepo for a product whose packages normally change together, especially when the team already has reliable tooling for affected code and tests. I also wouldn't migrate an established codebase just because a badly configured bot produced one impressive invoice. Fix the obvious over-reading first, then compare cost, useful findings, and missed bugs on representative PRs.

But if I can already understand, test, and ship a service on its own, I would give it its own repo. Share the review workflow and keep the starting point small. There'll still be dependencies to follow, of course. I just don't want every review to begin by working out which part of the company it belongs to. And I suspect the model will do a better job when we stop handing it forty files to find the one that matters.

I would rather spend the time fixing checkout than maintaining an explanation of why its reviewer is reading the admin redesign. The postcode still only needs validating.