Claude Monorepo Setup
Set up Claude Code for a pnpm monorepo with scoped CLAUDE.md files, path rules, safe permissions, and package-level verification commands.
Arrête de tout configurer. Place à la construction.
Des templates SaaS avec orchestration IA.
A good Claude Code monorepo setup gives the agent a map, not a dump of the entire repository. Put shared facts at the root, package-specific facts beside the package, and give Claude commands that verify changed workspaces and their dependents.
This tutorial adds that setup to an existing pnpm and Turborepo project with a Next.js 16 app, a worker, and shared database and UI packages. It does not scaffold the application code. The same structure works with npm workspaces or Yarn after you replace the package-filter syntax.
The finished structure
The important boundary is between global instructions and local instructions. The root file explains how the repository fits together. Nested files explain what is unusual about one app or package.
acme-platform/
├── .claude/
│ ├── rules/
│ │ ├── migrations.md
│ │ └── web-tests.md
│ └── settings.json
├── apps/
│ ├── web/
│ │ ├── app/
│ │ ├── tests/
│ │ └── CLAUDE.md
│ └── worker/
│ └── CLAUDE.md
├── packages/
│ ├── database/
│ │ ├── src/
│ │ └── CLAUDE.md
│ └── ui/
├── scripts/
│ └── verify-affected.mjs
├── CLAUDE.md
├── package.json
├── pnpm-workspace.yaml
└── turbo.jsonClaude Code loads project memory hierarchically. Anthropic's memory documentation recommends concise instruction files and supports both nested CLAUDE.md files and modular rules under .claude/rules/. It also notes that an instruction file can import another file with @path.
That hierarchy solves a common monorepo failure. A database migration rule should influence database work, but it should not occupy the context for a CSS change.
| Configuration | Best use | When Claude Code loads it |
|---|---|---|
Root CLAUDE.md | Repository map, shared commands, hard boundaries | At session start when you launch inside the repository |
Nested CLAUDE.md | Package-specific commands and constraints | When Claude reads files in that subtree |
Unscoped .claude/rules/*.md | Shared instructions split by topic | At session start |
Path-scoped .claude/rules/*.md | Rules for matching files only | When Claude works with a matching path |
.claude/skills/*/SKILL.md | Procedures used for particular tasks | When invoked or judged relevant |
The files are additive. A nested CLAUDE.md does not replace the root one, so contradictory instructions create ambiguity rather than a clean override. Check the active files with /context when behavior surprises you.
Create a reproducible workspace
The tutorial assumes Node.js 22 or newer and pnpm 11. Pin the package manager so Claude runs the same tool as developers and CI.
Use this root package.json. Every verification command is non-interactive and returns a useful exit code.
{
"name": "acme-platform",
"private": true,
"packageManager": "pnpm@11.17.0",
"engines": {
"node": ">=22"
},
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"test": "turbo run test",
"typecheck": "turbo run typecheck",
"verify:affected": "node scripts/verify-affected.mjs"
},
"devDependencies": {
"turbo": "^2.10.7"
}
}Declare the directories pnpm may treat as workspaces.
packages:
- "apps/*"
- "packages/*"Turborepo needs to know which tasks have outputs and which tasks may run together. Save this as turbo.json.
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"typecheck": {
"dependsOn": ["^typecheck"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}The commands matter more than the package manager. Claude should be able to run one command and learn whether its change is safe. If a command opens a watch process or asks a question, it is a poor agent verification command.
Write the root map
Keep the root CLAUDE.md below roughly 200 lines. Anthropic makes the same recommendation because excessively long instruction files consume context and are easier for a model to overlook.
The file should answer five questions: what lives where, which commands are canonical, what must never happen, how to verify a change, and where deeper instructions live.
# Acme platform
This repository is a pnpm and Turborepo monorepo.
## Map
- `apps/web`: Next.js 16 App Router product
- `apps/worker`: background jobs and scheduled work
- `packages/database`: schema, migrations, and database client
- `packages/ui`: shared React components and tokens
## Commands
- Install: `pnpm install --frozen-lockfile`
- Check one workspace: `pnpm --filter <workspace> lint`
- Test one workspace: `pnpm --filter <workspace> test`
- Verify changed workspaces: `pnpm verify:affected`
- Full verification: `pnpm lint && pnpm typecheck && pnpm test`
## Working rules
- Read the nearest nested `CLAUDE.md` before editing a package.
- Prefer package-filtered checks while iterating.
- Do not edit generated files, lockfiles, or migrations unless the task requires it.
- Do not import from another app. Shared code belongs in `packages/`.
- Never run a production migration or access production secrets.
- Ask before adding a new workspace dependency.
## Completion
Before finishing, run `pnpm verify:affected`.
If shared packages changed, also run their dependent apps' tests.
Report the commands run and any checks not run.This is deliberately operational. Style preferences that a formatter can enforce belong in formatter configuration, not in agent memory. The same principle is covered in the AGENTS.md versus CLAUDE.md guide.
Add local instructions
The web app needs rules that would be noise for the worker. Put this file at apps/web/CLAUDE.md.
# Web app
This workspace is a Next.js 16 App Router application.
## Commands
- Dev: `pnpm --filter @acme/web dev`
- Lint: `pnpm --filter @acme/web lint`
- Types: `pnpm --filter @acme/web typecheck`
- Unit tests: `pnpm --filter @acme/web test`
- Browser tests: `pnpm --filter @acme/web test:e2e`
## Boundaries
- Prefer Server Components. Add `"use client"` only at an interactive boundary.
- Server Actions must authenticate and validate their input.
- Read database data through `@acme/database`.
- Reuse `@acme/ui` before adding an app-local primitive.
- Keep route-specific components beside their route.
## Verification
Run lint, typecheck, and the nearest test after an edit.
Run browser tests when navigation, forms, authentication, or checkout changes.Database work has different risks. Put this at packages/database/CLAUDE.md.
# Database package
This package owns the schema, migrations, and typed client.
- Every schema change needs a forward migration.
- Never modify a migration that has shipped.
- Prefer additive changes, backfill, then removal.
- Queries used by the web app must have a bounded result or pagination.
- Tests use the disposable local database only.
Before finishing, run:
1. `pnpm --filter @acme/database lint`
2. `pnpm --filter @acme/database typecheck`
3. `pnpm --filter @acme/database test`Nested instructions should describe genuine local differences. Repeating every root rule in every package makes updates drift and wastes context.
Scope rules to paths
Claude Code rules can include YAML frontmatter with a paths field. This is the cleanest place for a safety rule that applies only when matching files enter the task context.
Save the migration policy as .claude/rules/migrations.md.
---
paths:
- "packages/database/migrations/**"
- "packages/database/src/schema/**"
---
# Migration safety
- Treat committed migrations as immutable.
- Generate a new timestamped migration for every schema change.
- Include a rollback note in the pull request even when rollback requires a forward fix.
- Do not run a remote migration from Claude Code.
- Verify locally against an empty database and a copy of the previous schema.Save browser-test guidance as .claude/rules/web-tests.md.
---
paths:
- "apps/web/app/**"
- "apps/web/tests/**"
---
# Web verification
- Prefer role or label selectors over CSS selectors.
- Test visible user behavior rather than React implementation details.
- Freeze time and seed deterministic data for screenshots.
- Do not update a snapshot until the visual difference has been reviewed.Path rules make instructions discoverable without one giant file. The Claude Code features overview also describes skills, hooks, subagents, and MCP as separate extension points. Do not force all four into the first setup. Memory plus deterministic commands is enough to start.
Set safe project permissions
Project settings are versioned, so allow only commands that every contributor should be able to run. Keep personal preferences in local settings.
Save this as .claude/settings.json.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(pnpm --filter * lint)",
"Bash(pnpm --filter * typecheck)",
"Bash(pnpm --filter * test)",
"Bash(pnpm verify:affected)",
"Bash(git diff *)",
"Bash(git status *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(pnpm publish *)",
"Bash(pnpm * publish *)",
"Bash(pnpm run deploy *)",
"Bash(pnpm * deploy *)",
"Bash(turbo run deploy *)"
]
}
}Claude Code checks deny rules before ask and allow rules. It also parses compound shell commands rather than letting an allowed prefix approve everything after && or ;. Permission patterns are still a guardrail, not a security boundary around an untrusted repository. Read rules affect Claude's file tools, but they do not stop a Bash subprocess from reading the same path. Review the Claude Code permissions documentation before broadening an allowlist, and keep deployment credentials out of the agent environment.
Exclude unrelated team memory
Starting Claude in a package narrows the immediate task, but ancestor instructions still load. In a very large monorepo, another team's rules may be irrelevant or conflict with your package.
Anthropic provides claudeMdExcludes for this case. Put personal exclusions in .claude/settings.local.json, which should stay out of version control.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"claudeMdExcludes": [
"**/apps/legacy-admin/CLAUDE.md",
"**/teams/experiments/.claude/rules/**"
]
}Patterns are matched against absolute file paths. Use exclusions sparingly and never hide managed policy memory, which cannot be excluded. A team-wide architectural boundary belongs in the repository instructions, not in each developer's local filter.
Verify only affected workspaces
Running the entire repository after every edit is slow. Running too little lets a shared-package break reach CI. This script asks Turborepo to select affected workspaces, then runs the three main checks for that package graph.
Save it as scripts/verify-affected.mjs.
import { spawnSync } from "node:child_process";
const tasks = ["lint", "typecheck", "test"];
const result = spawnSync(
"pnpm",
["turbo", "run", ...tasks, "--affected"],
{
cwd: process.cwd(),
encoding: "utf8",
stdio: "inherit",
env: process.env,
},
);
if (result.error) {
console.error(`Could not start verification: ${result.error.message}`);
process.exit(1);
}
if (result.status !== 0) {
console.error("Affected workspace checks failed.");
process.exit(result.status ?? 1);
}
console.log("Affected workspace checks passed.");Turborepo documents --affected as a comparison between main and HEAD by default. Set TURBO_SCM_BASE or TURBO_SCM_HEAD to change those refs. In a shallow CI clone, fetch the full comparison range first. Turborepo treats every package as changed when the history it needs is unavailable.
For example, a repository whose integration branch is development can run this command in CI.
TURBO_SCM_BASE=origin/development pnpm verify:affectedThis design gives Claude a fast inner loop and a credible completion gate. It also reduces the temptation to say a change is finished after running only the nearest unit test.
Use a task contract
A monorepo prompt should name the boundary and acceptance test. This is stronger than "fix the settings page" because it tells Claude what not to touch.
In apps/web, add an account timezone selector.
Use the existing @acme/ui Select component. Persist through the existing
profile Server Action. Do not change the database schema.
Read the nearest CLAUDE.md before editing. Run the web app lint,
typecheck, and relevant tests. Then run pnpm verify:affected.For a cross-package change, say so explicitly and name the dependency direction.
Add an optional avatarUrl field to packages/database and render it in
apps/web. Create a new migration, update the typed query, and add tests
in both workspaces. Do not edit previous migrations.If Claude repeatedly explores unrelated folders, the problem is usually a vague task or missing repository map. More tokens are not the first remedy. The context management guide explains when to reset, compact, or split a task.
Failure modes to avoid
One giant root instruction file feels convenient until every package has exceptions. Split only on real boundaries.
Do not duplicate lint rules in prose. The linter is executable and always current. Tell Claude to run it.
Do not give unrestricted shell access merely to avoid permission prompts. A short allowlist for tests and diffs still supports most coding work.
Do not make the full monorepo test suite the only feedback loop. Package-filtered checks keep iteration quick, while the affected graph catches shared-package impact before completion.
Finally, do not assume memory replaces architecture. If every app imports private files from every other app, Claude will reproduce that coupling. Fix the dependency boundary in code and enforce it with lint rules.
A practical operating loop
Start Claude from the repository root for a cross-package task and from a workspace directory for a local task. Ask it to read the relevant instruction file, inspect the package scripts, and propose the narrowest verification command before it edits.
During implementation, run filtered checks. At completion, run pnpm verify:affected, inspect git diff, and ask Claude to report what it could not verify. For an unattended workflow, add explicit limits and review the AI orchestration framework before granting write access.
That is the whole monorepo setup: a short map, local rules, scoped permissions, and verification commands that reflect the dependency graph. It gives Claude enough context to act without pretending the entire repository belongs in one prompt.
Claude Code monorepo FAQ
Does Claude Code work with pnpm monorepos?
Yes. Claude Code runs the commands exposed by the repository, so pnpm, npm, and Yarn workspaces are all viable. Give Claude package-filtered commands and a root map that names each workspace.
Where should CLAUDE.md go in a monorepo?
Put shared architecture and commands in the root CLAUDE.md. Add a nested file only where an app or package has different commands, safety constraints, or ownership.
How do I keep Claude Code out of unrelated packages?
Launch Claude inside the target workspace, use path-scoped rules, and name the package boundary. claudeMdExcludes hides irrelevant instruction files, not source packages. Use permission rules for secrets, and expect Claude to read a dependency when the task crosses that boundary.
If you want a ready-made multi-agent engineering harness instead of assembling the workflow yourself, the Code Kit is a $29 one-time purchase. It does not include model access, and Claude Code still requires a paid Anthropic plan.
Posted by @speedy_devv
Arrête de tout configurer. Place à la construction.
Des templates SaaS avec orchestration IA.
Route Subagent Models
Route Claude Code subagents to Haiku, Sonnet, or Opus by task, with complete agent files, safe tools, and a testable workflow.
CI Repair Agent
Build a guarded Claude Code CI repair agent that reads failed GitHub Actions logs, proposes a bounded patch, and leaves verification and merge control to CI.