Home / Articles

Structuring Claude Code Projects So Agents Stay Focused in Big Repos

Six patterns for large codebases: layered CLAUDE.md files, skills, subagents, deny rules, sparse worktrees and language-server plugins that keep agent context clean.

Publisher: REACTAPP.TOP10 min read2372 words

This article is published in English.

Sources: How I Structure Claude Code Projects So Agents Don’t Get Lost in Large Codebases

Picture a simple request to Claude Code: fix a session-timeout bug in the authentication middleware. The real fix is three lines. Yet before reaching them, the agent wanders through the logging system, the migration history, half of the API endpoints and, for reasons nobody can explain, the Terraform configs. By the 147th file read, the developer is hitting Escape.

The model isn't the culprit in a scene like that. The project layout is. Every convention, build command and architectural decision had been piled into one root CLAUDE.md of 340 lines, which the agent read faithfully at startup before spending the rest of its budget exploring code unrelated to authentication.

That's what happens when an agent is treated like a new hire who must read the entire company handbook before correcting a typo. It needs the right context at the right moment and not everything at once. Six patterns that fix this follow, along with a way to judge when each is worth using.

Why context runs out quickly

Claude Code's context window, around 200,000 tokens on the model used here, sounds enormous until you list what counts against it: each message you send, every file read, all command output, every CLAUDE.md instruction and any auto-saved memory notes. A token is about four characters.

In a big codebase, 50,000 tokens can disappear before a line of code gets written. A 300-line root CLAUDE.md costs roughly 1,200 tokens, a 500-line file read another 2,000, and exploring with grep or find adds more. By the time the agent understands the task, half the window may be gone. As it fills, performance drops: earlier instructions get forgotten, files are re-read and mistakes appear that a clean context wouldn't produce. Bigger windows aren't the answer. Keeping irrelevant material out of the window is.

Pattern 1: layered CLAUDE.md files

The first mistake is stuffing everything into the root file: build commands for the API, frontend component patterns, migration rules, deploy checklists and test conventions together.

Claude Code reads CLAUDE.md files from the working directory up to the repository root when a session starts. Start it in a subdirectory and it picks up that folder's file plus every parent's up to the root. So split the content by scope:

monorepo/
  CLAUDE.md                     # repository-wide rules only
  packages/
    api/
      CLAUDE.md                 # API-specific conventions
      .claude/skills/
      src/
    web/
      CLAUDE.md                 # frontend-specific conventions
      .claude/skills/
      src/
    shared/
      CLAUDE.md                 # shared library conventions
      src/

The root file keeps only repository structure and commit conventions, around 40 lines, and each package carries its own stack-specific details. Launch the agent in packages/api/ and it loads the root file plus the API one, never seeing frontend conventions. Switch to packages/web/ and the same logic gives a different slice of context.

A root file might read:

# Repository Structure

This is a monorepo with three packages under packages/:

- packages/api: Node.js REST API with Express, TypeScript, PostgreSQL
- packages/web: React frontend with Vite, TypeScript, TailwindCSS
- packages/shared: shared TypeScript utilities

Run commands from the package directory, not the monorepo root.
Each package has its own tsconfig.json, package.json, and test suite.

# Commit Conventions

- Prefix commits with the package name: "api: fix session timeout"
- One commit per logical change
- Run tests before committing

And the API package adds only what matters inside that package:

# API Package

This is the REST API server.

## Commands

- Run tests: `npm test` (uses Vitest)
- Run dev server: `npm run dev` (port 3001)
- Database migrations: `npm run migrate`
- Environment variables: copy `.env.example` to `.env`

## Code Patterns

API routes are in src/routes/. Each route file exports an Express router.
Database queries use Knex in src/db/. Never write raw SQL in route handlers.

## Testing

Tests are in src/__tests__/ mirroring the src/ directory.
Use supertest for HTTP assertions, not raw fetch.
Always wrap database tests in a transaction that rolls back.

The effect in this example is 80 lines of loaded context in place of 340, and the authentication fix reads 12 files rather than 147.

Pattern 2: skills for knowledge on demand

Even a layered setup can bloat. Testing patterns, deploy checklists and debugging runbooks are procedures and not facts, and the agent doesn't need them at startup, only when the task calls for them. That's the job of skills.

Skills live under .claude/skills/ and load when Claude judges them relevant, or when invoked explicitly through a slash command. A skill's description says when to use it and its body holds the actual content. Here, testing guidance moves out of the API file into a skill:

# .claude/skills/api-testing/SKILL.md

---
name: api-testing
description: Testing patterns for the API package. Use when writing or modifying tests in packages/api/.
---

## Test Structure

Tests are in `src/__tests__/` mirroring the `src/` directory structure.
Each route file has a corresponding `.test.ts` file.

## Running Tests

- All tests: `npm test`
- Single file: `npm test -- src/__tests__/routes/users.test.ts`
- Watch mode: `npm test -- --watch`

## Test Utilities

- `src/__tests__/helpers/db.ts`: provides `setupTestDb()` and `teardownTestDb()`
- `src/__tests__/helpers/auth.ts`: provides `createTestUser()` and `getAuthToken()`

## Patterns

- Use `supertest` for HTTP assertions, not raw fetch
- Always wrap database tests in a transaction that rolls back
- Mock external services in `src/__tests__/mocks/`

Ask for tests on the auth middleware and the agent loads the testing skill and follows its patterns. Ask it to fix the timeout bug and the skill stays unloaded, since testing isn't relevant.

Skills also suit repeatable workflows. A skill called with /fix-pr-comments could read review comments on a GitHub pull request, work out the requested changes, edit the code, run tests and push. The whole procedure lives in the skill and not in a CLAUDE.md that consumes context every session.

The rule of thumb: facts needed every session go in CLAUDE.md, while procedures and reference material become skills.

Pattern 3: subagents for isolated exploration

Even with layers and skills, exploration reads too much. "How does authentication work?" can turn into 40 file reads as the agent follows login, session storage, token refresh and logout, and all of that text stays in the main conversation after the question is answered, polluting the rest of the session.

A subagent runs in its own context window, does the exploring and sends back only a summary. The file reads and grep output remain in its context. The main thread gets the summary, which keeps it clean. A research request might look like this:

Use a subagent to investigate how our authentication system handles
session timeout and token refresh. Report back what files are involved
and how the flow works.

The subagent reads whatever it needs, traces the flow and reports back, so the main conversation receives a clean explanation of about 200 words and not the content of 40 files. Subagents also help for review after an implementation:

Use a subagent to review the session timeout fix for edge cases
and consistency with our existing auth patterns.

The reviewing subagent sees the diff fresh, without the reasoning that produced the change.

One caution: summaries lose detail. If you need exact line numbers or code excerpts, the main agent may have to reread files the subagent already saw. Subagents keep the main thread on implementation while exploration happens elsewhere.

Pattern 4: keeping generated and vendored files unread

Anything listed in .gitignore is skipped by default, which keeps node_modules/, dist/ and build/ out of search results. Some generated or vendored code is committed anyway: compiled protobuf definitions, vendored SDKs, generated GraphQL types. The agent will happily open these if a search surfaces them, though it never should.

Permission deny rules block chosen file patterns from being read. The general shape is below, and the exact syntax should be checked against the documentation for your Claude Code version:

# packages/api/.claude/settings.json

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

When a search for "GraphQL resolvers" now finds a generated schema file, the result shows up but the Read tool answers with permission denied, and the agent usually asks where the real source lives. A side effect worth knowing: the same rules stop reads you request by hand in the chat window. Ask it to open src/schema.generated.ts and it's refused, remove the rule temporarily. The payoff is that generated noise stays out of context and the agent is steered toward real source.

Pattern 5: sparse worktrees

Claude Code can create git worktrees for parallel or isolated work, and by default each one checks out the whole repository. In a monorepo that's wasteful, since an API fix doesn't need the frontend.

Git's sparse-checkout writes only chosen directories to disk and leaves the rest in the index, and the worktree.sparsePaths setting builds on it. Verify the config key against the documentation for your version:

# packages/api/.claude/settings.json

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

Worktrees then hold only .claude/, packages/api/ and packages/shared/ plus root files such as package.json and tsconfig.base.json. They're quicker to create and smaller on disk. If the agent tries to read a file that isn't checked out, git raises an error and the agent typically asks which directories exist. Sparse-checkout needs git 2.25 or newer, which git --version will confirm.

Pattern 6: code intelligence instead of file scanning

Finding where a function is defined or used in a large codebase takes many grep calls and file reads, as the agent scans results, opens candidates and keeps hunting.

Code intelligence plugins connect the agent to a language server. In place of scanning, it asks where refreshAuthToken is defined and receives a file and line number immediately. For TypeScript projects, the official LSP plugin installs like this, though install syntax varies by version, so check the docs:

/plugin install typescript-lsp@claude-plugins-official

Ask where refreshAuthToken is used and the agent queries the language server and gets back:

src/middleware/auth.ts:47
src/routes/users.ts:103
src/__tests__/auth.test.ts:22

No file reads were needed, so answers are faster and context stays clean. The plugin also reports type errors after edits without running tsc, so mistakes surface at once. It pairs well with deny rules: those block noise, and code intelligence cuts the need to scan what's left.

All six together

With every pattern applied, the original bug fix goes roughly like this:

  1. Start Claude from packages/api/.
  2. It loads the root CLAUDE.md (40 lines) and the API one (50 lines).
  3. You ask it to fix the session timeout bug in the auth middleware.
  4. It sends a subagent to investigate the auth flow, which reads about 20 files in its own context.
  5. The subagent reports that the timeout logic sits in src/middleware/auth.ts at line 47 and uses the wrong comparison operator.
  6. The main agent reads that file, fixes the bug, writes a test and runs it, touching about four files in the main context.

In this illustration the before and after look like 147 file reads, three failed attempts and 35 minutes against about four reads, one successful attempt and 6 minutes. Treat those as one developer's anecdote and not a benchmark, but the direction is what matters: the structure removed the obstacles between question and answer.

Picking what you need

Not every project needs all six. A single repo with 50 files doesn't need layered files or sparse worktrees. Once the codebase outgrows what one person can hold in their head, they start to pay off:

  • Layered CLAUDE.md files fit when you scroll past irrelevant sections in the root file, or when teammates ask why the API file contains frontend rules.
  • Skills fit when a section of CLAUDE.md has become a procedure or checklist, or you keep pasting the same instructions into chat.
  • Subagents fit when an investigation ends in "read these 30 files", or when you want a review without bias.
  • Deny rules fit when the agent keeps opening generated or vendored code in spite of .gitignore.
  • Sparse worktrees fit when a monorepo takes over 10 seconds to check out or you spawn many worktrees in parallel.
  • Code intelligence plugins fit when you work in a typed language and the agent spends more time locating definitions than writing code.

The goal isn't to apply everything blindly. It's to notice when context is getting cluttered and pick the pattern that cleans it. Other layouts can work for big codebases too, yet these six turn an agent that reads everything into one that reads the right files, writes the fix and stops.