Vibe Code Textbook

Workflows · · 1,622 words · 7 min read

Monorepo strategies for coding agents: what loads and what runs

How Claude Code, Codex and Gemini CLI find instruction files in a monorepo, how Nx, Turborepo and pnpm scope what runs, and a script that resolves both.

monorepo context files claude code codex nx

A monorepo asks a coding agent two questions that people usually run together and shouldn't: which instructions should be in context for this task, and which packages should the checks run over. The first is answered by the harness, from files on disk, by rules that differ in ways that change what reaches the model. The second is answered by the build tool, from the dependency graph, and has nothing to do with the agent at all.

Everything below is quoted from documentation read on 2026-09-17, at these versions from each project's releases page the same day: Claude Code v2.1.274 (2026-09-17), Codex CLI rust-v0.154.0 (2026-09-09), Gemini CLI v0.60.0 (2026-09-15), Nx 22.7.12 (2026-09-10), Turborepo v2.10.13 (2026-09-14), pnpm v12.4.2 (2026-09-15). No agent ran for this article. What ran is a standard-library Python script, on Python 3.13.12 on Windows, against a synthetic three-package fixture it writes itself; its output is verbatim and is fixture output, not a measurement of any real repository.

Which instruction files load

All three harnesses walk a directory chain, and all three concatenate rather than override. The differences are where the walk starts, where it stops, and what happens when the total gets large.

Claude Code "loads CLAUDE.md and CLAUDE.local.md from your current working directory and every directory above it", ordered "from the filesystem root down to your working directory", so "instructions closer to where you launched Claude are read last". Files below you are handled separately: Claude "also discovers CLAUDE.md and CLAUDE.local.md files in subdirectories under your current working directory. Instead of loading them at launch, they are included when Claude reads files in those subdirectories."

Codex builds the same kind of chain with a hard cap. Starting from the Git root it "walks down to your current working directory", checking AGENTS.override.md, then AGENTS.md, then project_doc_fallback_filenames in each directory. "At most one file per directory is included, and files are read in order from root to leaf and joined together with blank lines." Then the limit: "Codex skips empty files and stops adding files once the combined size reaches the limit defined by project_doc_max_bytes (32 KiB by default)."

Gemini CLI searches "for GEMINI.md files in your configured workspace directories and their parent directories" and adds a lazy pass that Claude Code's on-demand loading resembles: "When a tool accesses a file or directory, the CLI automatically scans for GEMINI.md files in that directory and its ancestors up to a trusted root." The search is bounded by context.discoveryMaxDirs, "Maximum number of directories to search for memory", default 200, and by context.memoryBoundaryMarkers, "File or directory names that mark the boundary for GEMINI.md discovery", default [".git"].

Claude Code v2.1.274 Codex rust-v0.154.0 Gemini CLI v0.60.0
File name CLAUDE.md, CLAUDE.local.md AGENTS.md, AGENTS.override.md GEMINI.md (context.fileName)
Walk cwd and every parent, at launch Git root down to cwd workspace dirs and their parents
Files per directory both, local last at most one one
Below the cwd on demand when Claude reads there not searched just-in-time on tool access
Total size limit none stated; 4 MiB per file skipped 32 KiB for the whole chain none stated
Exclusions claudeMdExcludes globs not stated on the pages read boundary markers, discoveryMaxDirs

The shared convention underneath is the nesting rule that /posts/context-files-claude-md-agents-md-cursor-rules.html covered as a single-repo idea. The AGENTS.md home page states it for monorepos directly: "Place another AGENTS.md inside each package. Agents automatically read the nearest file in the directory tree, so the closest one takes precedence and every subproject can ship tailored instructions", and claims "at time of writing the main OpenAI repo has 88 AGENTS.md files".

The cap drops the file you most wanted

The 32 KiB limit reads like a generous ceiling until you notice the order it interacts with. The chain is assembled root to leaf, and a monorepo's root instruction file is the one that accumulates every team's conventions. Here is the script resolving all three chains for a working directory of packages/api in the fixture, whose root instruction file has grown to 28 KB:

  Claude Code (CLAUDE.md chain, launch-time)
        232 B  ..\CLAUDE.md                                   loaded at launch
       5731 B  api\CLAUDE.md                                  loaded at launch
       5963 B  TOTAL IN CONTEXT (2 file(s) found)

  Codex (AGENTS.md chain, cap 32768 B)
         48 B  ..\home\.codex\AGENTS.md                       included, running total 48 B
      28339 B  ..\AGENTS.md                                   included, running total 28387 B
       5731 B  api\AGENTS.md                                  dropped: would pass project_doc_max_bytes (32768 B)
      28387 B  TOTAL IN CONTEXT (3 file(s) found)

  Gemini CLI (GEMINI.md chain)
         48 B  ..\home\.gemini\GEMINI.md                      global
        232 B  ..\GEMINI.md                                   workspace or parent
       5731 B  api\GEMINI.md                                  workspace or parent
       6011 B  TOTAL IN CONTEXT (3 file(s) found)

The package's own instructions, the most specific file in the tree and the reason nested files exist, are the ones that fall off the end. Nothing warns you: the chain is valid, the root conventions are present, and the rule that "files closer to your current directory override earlier guidance" cannot apply, because the closer file was never added. The defence is to keep the root file small rather than to raise project_doc_max_bytes, which is the same advice Claude Code gives for a different reason: "target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence."

Two details in that output are assumptions, and the script header says so. Whether the global ~/.codex/AGENTS.md counts toward the same 32 KiB budget is not stated on the page I read, and whether a file that straddles the limit is truncated or skipped entirely is not stated either; the script skips it and counts the global file.

Starting position matters as much as file size. Run the same resolution from the repository root and Claude Code's launch-time chain is 232 B, not 5,963 B: the package file is not loaded until Claude reads a file in that package. Claude Code's monorepo guide puts the trade-off in a table, and its own summary of starting from a subdirectory is that the session "Loads the root CLAUDE.md and packages/api/CLAUDE.md, skips packages/web/CLAUDE.md". Start at the root when "Tasks span multiple packages or subsystems"; start in the package when "Work is scoped to one package or subsystem".

Reaching the sibling package

Scoping down creates the obvious problem: shared types live next door. Each tool has a control for this, and Claude Code's two behave differently. additionalDirectories and --add-dir both grant read and write access, but the guide's table is explicit that the settings entry loads neither instructions nor skills from that directory ("Never" in both columns), while --add-dir loads skills and loads CLAUDE.md only when CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 is set. Codex's equivalent is sandbox_workspace_write.writable_roots, "Additional writable roots when sandbox_mode = workspace-write". Gemini CLI has context.includeDirectories, "Additional directories to include in the workspace context", paired with context.loadMemoryFromIncludeDirectories, default false, which decides whether those directories' GEMINI.md files come with them. In all three, file access and instruction loading are separate grants, and the default on the instruction half is off.

What actually runs

The second question is older than agents and already solved. Nx computes the set in three documented steps: "Use Git to determine the files you changed in your PR", "Use the project graph to determine which projects the files belong to", "Determine which projects depend on the projects you modified". Its CLI reference defines the result plainly: "Affected projects are projects that have been changed and projects that depend on the changed projects." The defaults are local-shaped, not CI-shaped: "The default base is your main branch, and the default head is your current file system."

Turborepo spells the same selection as a filter. --affected "is equivalent to --filter=...[main...HEAD]", where [] marks a Git specifier and the leading ... selects dependents. pnpm's filter syntax is the same vocabulary in a package manager: "To select an exact package, just specify its name", a trailing ellipsis adds dependencies, a leading ellipsis adds dependents, and --filter "...[origin/master]" selects packages changed since a ref.

Nx 22.7.12 Turborepo v2.10.13 pnpm v12.4.2
Changed set nx affected -t test turbo run test --affected pnpm --filter "...[origin/main]" test
Set the range --base, --head TURBO_SCM_BASE, TURBO_SCM_HEAD inside the brackets
Bypass Git --files not stated on the page read not stated on the page read
Add dependents default leading ... leading ...
Narrow further --exclude combine with --filter combine filters

For an agent, the value is that the check it runs after editing is proportional to the edit. Put that command in the instruction file that survives the chain above, and the loop from /posts/tests-first-with-an-agent-red-green.html stays fast in a repository where running everything does not.

Blast radius is a property of the graph

The catch is that "affected" is not a small set by nature. The script's second subcommand maps changed files to owning packages and then adds dependents transitively, from package.json alone:

directly changed packages: @demo/shared
in scope with dependents: 3 of 3 -> @demo/api, @demo/shared, @demo/web
share of the workspace a changed-set run would cover: 100%

One line in a shared library puts the entire fixture workspace in scope; the same command against a leaf package selects 33%, and a root-level file such as package.json belongs to no package at all. That last case is worth knowing before you rely on a changed-set run in CI, and it is a property of how the repository is factored, not of which tool computed it. The script is a stand-in, not a reimplementation: it reads no project graph, no lockfile, no tsconfig path mapping and no task pipeline, so a real run can legitimately select more.

The CSV in the box below is the comparison, not the fixture:

_README  monorepo-strategies-for-coding-agents.csv
tool           Claude Code | Codex | Gemini CLI | Nx | Turborepo | pnpm
version_read   release tag the documentation was matched to on 2026-09-17
layer          instructions | file access | checkout | task selection
control        the setting, flag, or file convention as documented
scope_unit     what the control is applied to: directory, glob, package, git range
default        documented default, or "unset"
what_it_does   the documented effect, quoted or closely paraphrased
source_url     the page each row was read from

What the pages do not say

Codex's AGENTS.md page does not say whether the global file counts toward project_doc_max_bytes, nor what happens to a file that crosses the limit. Gemini CLI's page states the discovery order but not the concatenation order, and does not say what happens when discoveryMaxDirs is reached. Turborepo's and pnpm's pages I read describe no equivalent of Nx's --files escape hatch for repositories without Git. None of the six pages measures whether any of this improves agent output; the effects here are on what enters context and what a command runs, which is where the evidence stops.

Two habits follow. Keep the root instruction file short enough that the per-package file still fits under the cap, and check where your agent starts: in a monorepo the working directory is the scoping decision every other setting layers on top of.

Code and data

Sources

  1. Anthropic, "How Claude remembers your project" (Claude Code docs, read 2026-09-17)
  2. Anthropic, "Set up Claude Code in a monorepo or large codebase" (Claude Code docs, read 2026-09-17)
  3. anthropics/claude-code, "Release v2.1.274" (read 2026-09-17)
  4. OpenAI, "Custom instructions with AGENTS.md" (Codex docs, read 2026-09-17)
  5. OpenAI, "Configuration Reference" (Codex docs, read 2026-09-17)
  6. openai/codex, "Release rust-v0.154.0" (read 2026-09-17)
  7. google-gemini/gemini-cli, "Project context (GEMINI.md)" at tag v0.60.0 (read 2026-09-17)
  8. google-gemini/gemini-cli, configuration reference at tag v0.60.0 (read 2026-09-17)
  9. google-gemini/gemini-cli, "Release v0.60.0" (read 2026-09-17)
  10. AGENTS.md, project home page (read 2026-09-17)
  11. Nx, "Run Only Tasks Affected by a PR" (read 2026-09-17)
  12. Nx, "affected" CLI reference (read 2026-09-17)
  13. nrwl/nx, "Release 22.7.12" (read 2026-09-17)
  14. Turborepo, "run" reference, --filter and --affected (read 2026-09-17)
  15. vercel/turborepo, "Release v2.10.13" (read 2026-09-17)
  16. pnpm, "Filtering" (read 2026-09-17)
  17. pnpm, "Release v12.4.2" (read 2026-09-17)