Skip to content

Claude Code in Monorepos: Scoped CLAUDE.md, Deny Rules, Worktrees

By Shoaib HossainJuly 31, 202610 min readUpdated October 17, 2026

In a small repository, Claude Code's defaults work: one CLAUDE.md, launch from the root, let it search. In a monorepo with dozens of packages, the same defaults fill the context window with instructions for code you are not touching and with file reads you did not need. The cost shows up as more tokens per task and as instructions that get followed less consistently.

This guide follows a three-package monorepo (packages/api, packages/web, packages/shared) and applies the settings Anthropic documents for large codebases: where to launch, how to split CLAUDE.md, how to keep committed generated code out of reads, and how to keep worktrees small. For how the context window fills and compacts, see Inside Claude Code's Context Window; for running parallel sessions, see Parallel Coding with Git Worktrees.

Four controls that keep a monorepo session focused: launch directory, layered CLAUDE.md, Read deny rules, sparse worktrees
Four controls that keep a monorepo session focused: launch directory, layered CLAUDE.md, Read deny rules, sparse worktrees

Where you launch decides what loads

The directory you start claude from sets three things at once: which files Claude can read and edit without an extra grant, which CLAUDE.md files load at launch, and which project .claude/settings.json applies.

Start from the repository root and Claude can reach every file, but only the root CLAUDE.md loads at launch; each package's CLAUDE.md loads on demand once Claude reads, writes, or edits a file in that package. Start from packages/api/ and Claude can reach only that subtree until you grant more, and it loads packages/api/CLAUDE.md plus every ancestor's file, including the root. Instructions from packages/web/ never enter the session.

One trap: project settings are not inherited the way CLAUDE.md files are. A session started in packages/api/ reads packages/api/.claude/settings.json, not the root's, so each starting directory's settings file must be self-contained.

Starting from the repo root versus a package directory changes file access and which CLAUDE.md files load
Starting from the repo root versus a package directory changes file access and which CLAUDE.md files load

Quick reference

  • Launch from the root for tasks that span packages; launch from a package for work scoped to it.
  • Run /context and check Memory files to confirm which CLAUDE.md files actually loaded.
  • If a task started in one package needs a sibling, grant it with --add-dir ../shared instead of relaunching from the root.
  • Files added with the additionalDirectories setting are readable and editable, but their CLAUDE.md and skills do not load.

Remember this

Pick the launch directory per task: root for cross-package changes, the package for focused work. It is the cheapest scoping control you have.

Split CLAUDE.md by directory

A single root CLAUDE.md in a monorepo either grows to cover every package, spending context on rules unrelated to the task, or stays too generic to help. Anthropic's guidance is a two-level split: the root file holds rules that apply everywhere, such as commit conventions, and each package's file holds that package's stack conventions.

Keep each file short; the docs target under 200 lines per CLAUDE.md, because longer files cost context and reduce adherence. Imports with @path organize a long file but do not reduce its cost, since imported files also load at launch. If two files contradict each other, Claude may follow either one, so treat edits to these files like documentation changes and review them in pull requests.

1<!-- CLAUDE.md (root) -->2Run package scripts from the package directory, not the monorepo root.3Prefix commit subjects with the package name, e.g. `api: add rate limiting`.4Never edit files under packages/*/generated/; run `npm run codegen` instead.5 6<!-- packages/api/CLAUDE.md -->7Copy `.env.example` to `.env` before running anything.8Write queries with the Knex query builder; no raw SQL in route handlers.9Never edit a merged migration; add a new one.

Quick reference

  • Path-scoped rules in .claude/rules/ with a paths: glob are the central alternative: they load only when Claude works with matching files.
  • Use claudeMdExcludes (for example "**/packages/web/**") to skip packages you never work in when launching from the root.
  • Move procedures that are needed only sometimes, like a release checklist, into skills; skills load only when relevant.
  • /doctor prompt-audit reports outdated or conflicting instructions across your instruction files.

Remember this

Root file for repository-wide rules, package files for stack conventions, skills for occasional procedures. Short files get followed.

Keep generated and vendored code out of reads

Claude's content searches already respect .gitignore, so node_modules/, dist/, and other ignored paths stay out of search results with no setup. The problem in a monorepo is committed noise: a vendored SDK, generated API clients, or checked-in build output that matches every search.

For those paths, add Read deny rules in permissions.deny. A .claudeignore file does nothing; Claude Code does not read it. Deny rules cover the built-in file tools, recognized Bash file commands such as cat and head when the path is an argument, and, on a best-effort basis, results from the built-in Grep and Glob tools. They do not cover a grep -r over a parent directory or a script that opens files itself.

End directory patterns with /**/* rather than /**: the rule then blocks everything inside the directory but still lets Claude list it or cd into it.

Read deny rules block committed vendored code; .gitignore already keeps ignored paths out of search
Read deny rules block committed vendored code; .gitignore already keeps ignored paths out of search

Quick reference

  • Commit shared rules to .claude/settings.json in each directory you launch from; personal rules go in .claude/settings.local.json.
  • Relative patterns like Read(./**/vendor/**/*) anchor at the session's working directory; use //-absolute paths in a root-level local file if you launch from subdirectories.
  • For enforcement every session must obey, put the rules in managed settings, which nothing else can override.
  • A code intelligence plugin (for example typescript-lsp) lets Claude jump to definitions and references instead of reading through files to find them.
Has no effect
1# .claudeignore  (not read by Claude Code)2vendor/3src/__generated__/4*.generated.ts
.claude/settings.json
1{2  "permissions": {3    "deny": [4      "Read(./**/vendor/**/*)",5      "Read(./**/__generated__/**/*)",6      "Read(./**/*.generated.*)"7    ]8  }9}

Remember this

Rely on .gitignore for ignored paths; use Read deny rules for committed noise. Never rely on .claudeignore.

Sparse worktrees and cross-package access

claude --worktree starts a session in a new Git worktree so its changes stay isolated from your main checkout. By default that worktree contains the whole repository. The worktree.sparsePaths setting uses Git sparse-checkout to write only the listed directories plus root-level files, so worktrees start faster and take less disk. This matters most when subagents each run in their own worktree.

List directories, not files, and include .claude if the worktree needs the root's settings or rules. Pair it with symlinkDirectories so every worktree links to the main checkout's node_modules instead of duplicating it.

1{2  "worktree": {3    "sparsePaths": [".claude", "packages/api", "packages/shared"],4    "symlinkDirectories": ["node_modules"]5  }6}

Inside a worktree, the working directory is the worktree root, so settings load from the root's .claude/settings.json. Put deny rules you need in worktree sessions there too.

worktree.sparsePaths writes only the listed directories plus root-level files into each new worktree
worktree.sparsePaths writes only the listed directories plus root-level files into each new worktree

Quick reference

  • sparsePaths entries are relative to the repository root, whichever directory you launched from.
  • All worktrees in a session share one sparsePaths list; if subagents need different packages, list them all.
  • Sparse checkout enables Git's extensions.worktreeConfig; Claude Code removes it after the last worktree only if it added it.
  • Plain git sparse-checkout set on your main checkout also works, but it changes your own working tree; sparsePaths scopes only Claude's worktrees.

Remember this

Use sparsePaths and symlinkDirectories so parallel worktrees stay small, and keep worktree-relevant settings in the repository root.

Hands-on: scope a three-package repo

Build the example monorepo and check each control with /context.

  1. Create packages/api, packages/web, and packages/shared, each with a short CLAUDE.md, plus a root CLAUDE.md.
  2. Add packages/api/vendor/sdk.js and commit it.
  3. In packages/api/.claude/settings.json, add "Read(./**/vendor/**/*)" to permissions.deny and "../shared" to permissions.additionalDirectories.
  4. Start claude from packages/api/ and run /context.
  5. Ask Claude to open vendor/sdk.js, then to edit a file in packages/shared/.

Pass criterion: Memory files lists the root and api CLAUDE.md but not web's; the vendor read is refused; the edit in packages/shared/ succeeds without relaunching.

Quick reference

  • If web's CLAUDE.md appears, check whether Claude touched a file under packages/web/, which loads it on demand.
  • If the vendor read succeeds, check the rule with /permissions and confirm the settings file is in the directory you launched from.
  • Repeat step 4 from the repository root and compare which memory files load.

Remember this

Seeing /context change with the launch directory makes the loading rules concrete faster than reading about them.

Key takeaway

Scaling Claude Code to a monorepo is mostly about deciding what never enters the session. Launch from the narrowest directory that covers the task, keep the root CLAUDE.md to repository-wide rules with package files for the rest, rely on .gitignore for ignored paths and Read deny rules for committed noise, and keep worktrees sparse.

None of these require new tools, and each one is checkable: /context shows what loaded, /permissions shows which rules apply.

Practice (20 min): run the exercise above, then write the deny rules and sparsePaths you would commit for your own largest repository.

Share:

References

  1. Claude Code docs: Set up Claude Code in a monorepo or large codebase — checked Oct 17, 2026
  2. Claude Code docs: How Claude remembers your project (CLAUDE.md) — checked Oct 17, 2026
  3. Claude Code docs: Configure permissions — checked Oct 17, 2026

Shoaib Hossain

Author, Core Concept Learning

Related Articles

Every request Claude Code sends to the model carries the whole session so far: the system prompt, your CLAUDE.md files, …

Read

AI coding agents change where engineering effort is spent: the hard problem moves from typing code to defining boundarie…

Read

Claude Code is an agentic coding tool that can inspect a repository, propose edits, run permitted commands, and work thr…

Read

Keep learning

Follow a structured path or browse all courses to go deeper.