Claude Code in a large codebase: scoping an agent to the part that matters
11m read time

Claude Code in a large codebase: scoping an agent to the part that matters

Coding agents start failing systematically past roughly 400,000 lines. What actually breaks, the Claude Code settings that fix it in a monorepo, and the ignore file everyone recommends, which has never existed.

A coding agent is asked to trace how Kubernetes allocates dynamic resources across packages. That means following the DRA system through 1.4 million lines of Go spread over 22,000 files.

It greps for the allocator. Hundreds of hits. It reads a file, follows an import, reads the next one. Every individual step is reasonable. After 6,000 seconds, over an hour and a half, it has produced nothing. Zero score.

Same model, same task, one difference: index-backed search instead of local file access. Eight keyword searches, six semantic searches, one find-references call. Done in 89 seconds, scoring 0.90 out of 1.0.

That demo comes from Sourcegraph's failure analysis of coding agents in large codebases (8 May 2026), and Sourcegraph sells code search, so weigh it accordingly. Their data is more useful than their demo.

The line sits at roughly 400,000 lines

Behind it is CodeScaleBench: 1,281 scored agent runs across 40+ of the largest open source repositories in nine languages. One finding is worth writing on the wall. Agents with only local tools, meaning grep, file read and glob, begin to struggle systematically once a codebase exceeds roughly 400,000 lines of code.

Below that line, adding code intelligence tools produces a reward delta of −0.080. Their own table annotates it: "Tools add overhead; grep typically works fine." Between 400K and 2M lines it flips to +0.259.

A vendor publishing a negative number for its own product category earns some trust back. It is also the most useful sentence in the piece, because it tells most readers they do not have the problem the piece is about.

Then the same researcher published a follow-up on 31 July, and it moves the line the May piece rests on. "The answer depends less on codebase size than on how the relevant work is distributed across it."

Their retrieval-difficulty weighting, fixed before they looked at outcomes, now weights repository span and directory dispersion above total lines: "A 40-million-line monorepo behaves like a small repository when the relevant code sits in one directory. A two-million-line estate becomes the harder retrieval problem when the answer is scattered across four services."

The same piece reports 288 tasks where both arms could reach the same code at the same revisions. Structured retrieval raised file-level F1 from 0.091 to 0.240 and recall from 0.120 to 0.272, and "Aggregate task-completion reward was effectively unchanged." A later census found roughly 74% of that corpus was solvable with grep alone.

Read the two together and the line count is a proxy for what actually hurts: how far apart the relevant code sits. That is the better framing for a tutorial anyway, because dispersion is the variable you can move. Every setting below moves it.

The line count is still the cheapest first reading, so I measured my own repositories against it. The largest, a customer-insight tool, is 162,781 lines of code across 679 source files. The MCP servers I maintain land between 40,000 and 90,000. This site is 11,781.

All of them sit under the line where buying retrieval infrastructure starts to pay for itself.

So most of what follows is configuration rather than procurement. That is the good news, because configuration is the half you control.

What actually breaks is file selection

It helps to know what you are optimising before you touch a setting.

An empirical study from April 2026 (arXiv 2604.05481) ran 500 SWE-bench Verified instances through GPT-5-mini across 61 context configurations, varying file-level, element-level and line-level context. Its headline finding: "more context does not consistently improve repair performance."

What did matter:

  • File-level localisation is the dominant factor, worth a 15-17× improvement over a no-file baseline.
  • Successful repairs cluster in configurations with approximately 6-10 relevant files.
  • Line-level context expansion frequently degrades performance through noise amplification.

Six to ten files is where the successful repairs clustered. Treat that as the rough shape of a well-scoped task. Every setting below is a way of raising the odds that the agent's first handful of reads are the right ones, and cutting what it pays for the ones that are not.

A bigger window does not fix this, for reasons I went into in the post on session hygiene.

Start Claude where the work is

The highest-leverage decision in a monorepo costs nothing and takes no config file: pick the directory you launch from.

Anthropic's monorepo setup guide puts it in a table. Start from the repository root and Claude can reach every file, but only the root CLAUDE.md loads at launch, with subdirectory files loading on demand as it reads there. Start from packages/api/ and you get that directory's file plus every ancestor's, with sibling packages out of scope entirely.

One thing to know before you commit to a subdirectory: project settings do not inherit the way memory files do. A .claude/settings.json at the repository root applies only when you start from the root. Each package's settings file has to stand on its own.

Cut what loads, then cut what it reads

Two settings, both verified present in Claude Code 2.1.246 on this machine. Both go in .claude/settings.json, committed for the team, or in .claude/settings.local.json if the choice is only yours.

claudeMdExcludes skips memory files by glob so another team's package never loads:

json
{
  "claudeMdExcludes": ["**/packages/web/**"]
}

The list is static. To focus on one package today and another tomorrow, start Claude from that package instead.

Read deny rules keep checked-in noise out of context. Content searches already respect .gitignore, so node_modules/ and dist/ are handled. The rules are for vendored SDKs and committed generated code:

json
{
  "permissions": {
    "deny": ["Read(./vendor/**)", "Read(./**/*.generated.*)"]
  }
}

Then stop the agent reading files to answer questions a compiler can answer. A code intelligence plugin wires Claude to a language server for go-to-definition and find-references:

shell
/plugin marketplace add anthropics/claude-plugins-official
/plugin install typescript-lsp@claude-plugins-official

The first line is only needed if the install reports the marketplace as missing, and the plugin needs the language's language server binary on your machine. You can tell it took by asking for the callers of a symbol you know is used in three places: the answer should arrive without a wall of file reads in front of it.

This is the direct fix for the failure Sourcegraph calls wrong file, wrong symbol. Their example: a grep for allocate in Kubernetes returns 47 matches with no ranking, while find-references returns one definition and twelve call sites sorted by role. Across their benchmark, keyword search was used 7,993 times against 2,449 semantic searches, so agents reach for the crudest tool available. Give them a better one and they use it.

Give worktrees less to check out

--worktree isolates changes in a fresh checkout, which I covered in the worktrees post. In a large repository the default behaviour, checking out the whole tree, is the expensive part. worktree.sparsePaths uses git sparse-checkout to write only what you list:

json
{
  "worktree": {
    "sparsePaths": [".claude", "packages/api", "packages/shared"],
    "symlinkDirectories": ["node_modules"]
  }
}

List directories, not files. Root-level files like package.json come along automatically. Root-level directories do not, which is why .claude is in that list. All worktrees in a session share the same paths, so if two subagents need different packages, list both.

Send a scout instead of going yourself

A subagent is a separate Claude instance with its own context window that does a task and returns only its final answer to the parent. For exploration in a big tree that property is the whole point: forty file reads happen somewhere else and one paragraph comes back.

The pattern that works is a narrow question with a named deliverable. "Find every call site of formatCurrency and report file paths with line numbers" returns six to ten files. "Have a look at the billing module" returns an essay assembled from reads you now have to pay for twice.

The ignore file that does not exist

Search for how to keep an agent out of a directory and something will tell you to write a .claudeignore file. It is in blog posts, in wiki pages, in inherited repositories.

There is no such file. There never has been.

Claude invented it. Asked how to stop the agent reading .env files, Claude told The Register that "You can add .env to a .claudeignore file in your project root", and that "Claude Code will refuse to read any files matching patterns listed there". Thomas Claburn tested that claim on 28 January 2026 against v2.1.12, with a test .env and a .claudeignore listing it. Claude Code read the file anyway.

Seven months on, I grepped the shipped 2.1.246 binary. sparsePaths appears 26 times, claudeMdExcludes four, additionalDirectories 41. claudeignore, in any casing, appears zero times. The changelog has never mentioned it either, in any release ever shipped.

This post nearly repeated the myth, which is the part worth telling. Researching it, I pulled Anthropic's enterprise guidance (14 May 2026) through a fetch that hands back a summary instead of the page, and it came back recommending ".claudeignore files to exclude generated files, build artifacts, and third-party code". The page itself says .ignore files, and recommends committing permissions.deny rules in the same bullet.

A model reading the correct sentence filled in the prefix it expected, because the wrong name is the one the internet repeats most. Run the same fetch again and it answers correctly, which makes it worse: an error you cannot reproduce is one you cannot test for. The fact-checking pass caught it against the raw HTML, which is the only reason you are reading this paragraph instead of the confident version.

Anthropic settled it in the open nine days ago. On an issue titled ".claudeignore not enforced by Bash or Edit tools", a Claude Code maintainer tested 2.1.233 with a .claudeignore and a secret in the file it named, and reported all four operations succeeding: Read, Grep, Bash cat and Edit. The file "isn't a Claude Code feature", and it "has never appeared in the changelog or docs, and no tool enforces it". Where it had seemed to work, "that was the model voluntarily complying after noticing the file", which the maintainer calls "soft, probabilistic behavior, not a security boundary".

If you inherit a repository with a .claudeignore in it, that file has never done anything.

The real mechanism is permissions.deny, and it is worth knowing exactly how far it reaches. The docs are candid: deny rules cover the built-in file tools and recognised Bash file commands, and "Claude still sees denied paths in the output of a Bash search such as grep -r or find." So a deny rule trims your context budget, and anything reaching for a shell can still read what it names. If you are denying a path because it holds secrets rather than because it holds noise, you want the wire-level view of what your agent actually sends instead.

Search the repository or index it

Sourcegraph's prescription is an index. Anthropic's is the opposite: Claude Code "navigates a codebase the way a software engineer would", traversing the file system and using grep. The argument is that at scale "embedding pipelines can't keep up with active engineering teams", so an index answers about the codebase as it was hours or days ago.

The two were published six days apart in May 2026. Both parties are selling something. Neither is wrong, because they are describing different sides of the 400,000-line threshold, and Anthropic's own setup guide quietly concedes the far side: if your organisation already runs a code search or RAG index over the repository, expose it as an MCP tool so Claude queries it instead of reading files.

Under the line, your agent is under-instructed, and the fix is a launch directory, a layered CLAUDE.md and a language server. Over the line, no amount of prompt care substitutes for something that can rank results structurally, and that is when you go shopping.

The measurement that tells you which side you are on is one command:

shell
git ls-files -z '*.ts' '*.tsx' '*.js' '*.jsx' '*.vue' '*.py' '*.php' '*.go' '*.rs' '*.java' '*.cs' \
  | xargs -0 cat | wc -l

Adjust the extensions to your stack. Pipe through cat rather than handing the files to wc -l directly. On a repository big enough to split the argument list, wc -l reports the last batch's total and says nothing about the rest, which is a poor way to find out you have the problem.

Counting lines is the cheap part. What decides it is where the answer to a normal question in your repository actually lives, and whether an agent can get there without opening the whole tree first.

(33 of 33)
01Getting the best out of Claude Code02Superpowers: teaching Claude Code to think before it types03Claude Code hooks: deterministic control over AI workflows04The CLAUDE.md file: give your AI permanent memory05Stop asking your agent nicely06What's new in Claude Code: notes from the London talk07The best number in Opus 4.8 isn't a benchmark08Stale memory is worse than no memory09The agent is just a loop10Build an MCP server, then ask whether it should exist11Skill, subagent, hook, or slash command? Pick the right one12Log in to MCP servers from your shell13The day 'default' became 'Manual'14How to write a proper Claude Code skill15How to write a proper Claude Code subagent16Claude Code permissions: the guide I wish the docs were17Sandboxing Claude Code: put your agent in a box that holds18Prompt injection defense for developers who ship agents19Which Claude model for which coding task20Refactoring legacy code with a coding agent: start with characterization tests21MCP server authentication: OAuth, scopes and rate limits22Opus 5 is here and your effort settings just expired23AI agent incident response: what to do when your coding agent goes wrong24Claude Code /doctor: the health check became a context audit25Claude Code context management: when to /clear and when to /compact26Git worktrees for parallel coding agents: what they isolate and what they share27Claude Code plan mode: decide before the agent writes28Debugging with a coding agent: give it the search, keep the hypothesis29Claude Code checkpoints and /rewind: how to undo an agent's changes30Claude Code cross-session messaging: how to make your sessions talk to each other31Sharing Claude Code config across a team: what a repo can and cannot enforce32Your Claude Code session has no clock33Claude Code in a large codebase: scoping an agent to the part that matters