DEV Community

Cover image for Claude Code in a Monorepo or Large Codebase: Setup Guide
Nixon Perinchery
Nixon Perinchery

Posted on Originally published at bridge-terminal.com Fully Autonomous

Claude Code in a Monorepo or Large Codebase: Setup Guide

The short version

  • In a large repository, keep Claude's context on the part of the code a task touches: unrelated instructions and file reads cost tokens and can degrade Claude's performance.
  • Where you launch claude decides which files it can edit without another grant, which CLAUDE.md files load at startup and which project settings apply, so start from the package you are working in when the work is scoped.
  • Per-directory CLAUDE.md files, claudeMdExcludes and Read deny rules keep other teams' conventions, generated code and vendored code out of context.
  • worktree.sparsePaths with symlinkDirectories makes worktrees lighter, and a code intelligence plugin lets Claude look up definitions and references through a language server instead of scanning files.
  • Deny rules do not stop a Bash search such as grep -r from listing denied files, so treat them as a context filter, not a security boundary.

To set up Claude Code in a monorepo or large codebase, scope it to the part of the tree each task touches: start it from the package you are working in, split instructions into per-directory CLAUDE.md files, and block reads of generated and vendored code. The defaults are tuned for smaller projects. In a big tree they can fill the context window with instructions and file reads unrelated to the task, which costs tokens and, according to Anthropic's large-codebase guide, degrades Claude's performance.

This article walks through that guide's settings in the order you would apply them, with config you can copy. Each setting is independent, so adopt the ones that fit your repository.

Why does a large codebase need different setup?

Instructions and file reads both grow with the size of the repository. A single root CLAUDE.md that covers every subsystem either grows long and mostly irrelevant to any one task, or stays so generic it helps nobody. File searches pull in generated and vendored code that no one wants Claude to read.

The fix is the same in each case: load less, and load it only when it matters. Everything below is a way to do that.

Where should you start Claude Code in a monorepo?

Where you run claude decides which files it can read and edit without another permission grant, which CLAUDE.md files load at launch, and which project settings apply. The docs give this split:

Start from File access CLAUDE.md loaded at launch
Repository root Every file Root only; subdirectory files load on demand
A subdirectory That subtree only, until you grant more That directory's plus every ancestor's

Start from the root when a task spans packages. Start from a package when the work stays inside it. One catch: project settings in .claude/settings.json are not inherited from parent directories the way CLAUDE.md files are. If you launch from packages/api/, the settings that apply are that directory's, so each subdirectory's settings file has to be self-contained.

How do you layer CLAUDE.md files by directory?

Keep the root file for rules that apply everywhere, and put area-specific conventions next to the code. Claude Code loads CLAUDE.md files from the working directory and every parent at launch, then loads each subdirectory's file on demand. Commit them so teammates inherit them; the directory's owners usually maintain their file.

The root file from the docs' example:

Run package scripts from the package directory, not the monorepo root.
Prefix commit subjects with the package name, for example `api: add rate limiting`.
Never edit files under packages/*/generated/. Run `npm run codegen` in the package instead.
Enter fullscreen mode Exit fullscreen mode

And packages/api/CLAUDE.md:

Copy `.env.example` to `.env` before running anything. Tests and the dev server fail without it.
Write database queries with the Knex query builder. Never put raw SQL strings in route handlers.
Never edit a migration after it has merged. Add a new migration instead.
Enter fullscreen mode Exit fullscreen mode

Started from packages/api/, Claude sees both files and nothing from packages/web/. To check what actually loaded, run /context all and look under Memory files. If a checked-in file has grown too large, the docs point to the /doctor checkup.

Three habits keep these files honest, all from the same guide:

  • Review CLAUDE.md edits in pull requests, like any documentation change.
  • Revisit them after major model releases, since a workaround for an older model's limitation can become overhead.
  • Consider a Stop hook. It receives the path to the session transcript when Claude finishes responding, so a script can propose CLAUDE.md updates while the gap is fresh.

Per-directory CLAUDE.md or path-scoped rules?

Both target instructions at part of the tree. Per-directory files live with the code and are maintained by that directory's owners. Rules under .claude/rules/ live centrally and load when Claude works with a file matching the rule's paths: glob, which suits conventions that apply to many scattered paths.

How do you exclude CLAUDE.md files you never need?

When you start from the root, subdirectory files can load on demand. The claudeMdExcludes setting skips files by glob so they never load. Patterns are matched against absolute file paths, so begin relative-style patterns with **/.

{
  "claudeMdExcludes": [
    "**/packages/web/**"
  ]
}
Enter fullscreen mode Exit fullscreen mode

This skips every CLAUDE.md and rules file under that package. Put it in .claude/settings.local.json if it is only for you. Claude Code adds that file to your global gitignore when it saves a setting there, but if you create it by hand, add it to gitignore yourself.

The list is static, not a per-task switch: to work on a different package tomorrow, start Claude from that package instead of editing exclusions. The setting works at any settings scope and arrays merge across scopes. A managed organisation-wide CLAUDE.md cannot be excluded this way.

How do you stop Claude reading generated and vendored code?

Content searches respect .gitignore by default, so node_modules/, dist/ and build/ are usually covered if they are listed there. For checked-in generated code or a vendored SDK, add Read deny rules:

{
  "permissions": {
    "deny": [
      "Read(./**/dist/**/*)",
      "Read(./**/build/**/*)",
      "Read(./**/*.generated.*)",
      "Read(./**/vendor/**/*)"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The patterns end in /**/*, not /**, so Claude can still list or cd into the directories but cannot open what is inside. Commit the rules to .claude/settings.json for the team, use .claude/settings.local.json for yourself, or use managed settings to enforce them for everyone. Relative patterns anchor at the session's working directory, so if you start sessions from subdirectories and keep the rules in the root's local file, write them as //-absolute paths.

Know the limits. Deny rules cover Claude's built-in file tools. In Bash they cover the file commands Claude Code recognises, such as cat, head, grep and find, when a denied path appears as an argument. A Bash search like grep -r or find over a directory that contains denied files still includes them, and deny rules do not cover subprocesses that open files themselves. Use them to save context, not as a security control.

Can Claude find code without scanning every file?

In a large tree, finding a symbol's definition or callers can cost many file reads and grep calls. A code intelligence plugin connects Claude to a language server so it can jump to definitions, find references and surface type errors directly. The common workflows page recommends installing one for your language. From the official marketplace, the TypeScript one installs at the claude prompt with:

/plugin install typescript-lsp@claude-plugins-official
Enter fullscreen mode Exit fullscreen mode

If Claude Code reports that the marketplace is not found, add it with /plugin marketplace add anthropics/claude-plugins-official and retry. If it reports the plugin is not found, refresh the catalog with /plugin marketplace update claude-plugins-official. Each developer's machine also needs the language's server binary. To enable a plugin for everyone in the repository, use the enabledPlugins project setting.

How do you keep worktrees light in a big repo?

claude --worktree starts a session in its own git worktree, a separate checkout on its own branch. By default it checks out the whole repository. The worktree.sparsePaths setting uses git sparse-checkout to write only the listed directories plus root-level files. Pair it with symlinkDirectories so node_modules is symlinked back to the main copy instead of duplicated:

{
  "worktree": {
    "sparsePaths": [
      ".claude",
      "packages/api",
      "packages/shared"
    ],
    "symlinkDirectories": [
      "node_modules"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Paths are relative to the repository root, whichever subdirectory you start from. Root-level files such as package.json and lock files always come along, but root-level directories do not, so list .claude if you want the root settings or rules inside the worktree. List directories, not individual files.

This also helps subagents that run in worktrees, since each gets a lightweight checkout. All worktrees in a session share the same sparsePaths, so list every package any subagent needs. If you set it in .claude/settings.local.json, the lists merge with the committed one: a local file can add paths but not remove them.

One gotcha: inside a worktree the working directory is the worktree root, so a package's own .claude/settings.json does not load there. Put settings you need in worktrees, such as deny rules or hooks, in the repository root's .claude/settings.json.

How do you edit across packages?

Started from packages/api/, Claude can read and edit files in that directory. Grant access to siblings with additionalDirectories or a flag:

{
  "permissions": {
    "additionalDirectories": ["../shared", "../web"]
  }
}
Enter fullscreen mode Exit fullscreen mode
claude --add-dir ../shared
Enter fullscreen mode Exit fullscreen mode

The two differ in what else loads. Directories from the additionalDirectories setting never load their CLAUDE.md, rules or skills. With --add-dir or /add-dir, skills load, and CLAUDE.md and rules load only if you set an environment variable:

CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 claude --add-dir ../shared
Enter fullscreen mode Exit fullscreen mode

For a change that crosses packages, such as a shared type and its call sites, the guide suggests giving Claude the whole change in one session and planning first in plan mode. Claude writes the plan to a file, and Claude Code re-injects that file after each compaction, so the plan survives a long session.

Where do skills and plugins fit?

Skills under a directory's .claude/skills/ load on demand when Claude determines they are relevant. Claude picks a skill by reading every discovered skill's name and description, and only the chosen skill's full content enters context. So area-specific procedures, such as a package's testing patterns, cost little until needed. A skill's paths frontmatter can also scope it to matching files by glob.

Keep descriptions short and lead with the words a request would contain, because when there are many skills, some lose their descriptions entirely. Started from the repository root, the skills in scope include those from every subdirectory Claude touches, which can accumulate into the hundreds.

When per-directory files become hard to govern, the guide recommends moving conventions into skills, versioned plugins owned by a platform team, or an MCP server over a code search or RAG index you already run.

A minimal starting point

If you do nothing else, do these four things:

  1. Commit a short root CLAUDE.md and one per package for local conventions.
  2. Start Claude from the package, not the root, when work is scoped.
  3. Add Read deny rules for committed generated and vendored paths.
  4. Set worktree.sparsePaths and symlinkDirectories before running parallel sessions.

Then run /context all once and confirm only what you expect is loaded.

What Atlas does today

Atlas is a team of AI engineers built on Claude Code. Each engineer is its own Claude Code session with its own copy of the code to work in, loads only the tools its role uses, and loads the project's instructions once. Atlas also builds and keeps a code map of the project so engineers can find their way around a large codebase. Several engineers at once use your Claude plan's limits faster, so keeping each one's context narrow matters there too.

Atlas does not yet set up language-server lookups or read blocking for generated and vendored code. That is coming to Atlas, built in, so it will need no setup. Until then, you can apply the deny rules and plugin install above in your own project's settings.

Conclusion

Claude Code does not need a different tool for a large repository, just narrower scoping. Choose the launch directory deliberately, layer instructions by area, keep generated code out of reads, and keep worktrees sparse. Revisit the setup after model releases, because instructions that were once necessary can turn into overhead.

Sources


Originally published on the Atlas blog.

Top comments (0)