DEV Community

Sangmin Lee
Sangmin Lee

Posted on • Originally published at claudeguide.io

Claude Code Monorepo Guide: CLAUDE.md & Cost Control (2026)

Originally published at claudeguide.io/claude-code-monorepo-guide

**Claude Code in monorepos needs 3 patterns to scale beyond ~50 files: per-package CLAUDE.md (isolates context), scoped edits via cd packages/<pkg

Cost control per workspace

Anthropic's Admin API exposes a workspace_id on every API request and on the usage and cost reports. In a monorepo you have two good options:

  1. One Anthropic workspace per package. Heavier setup, but the cleanest reporting — you get cost-per-package directly out of the Admin API with no tagging.
  2. One workspace, custom header tagging. Use a single workspace_id and tag each request with a x-package header (or pass it as a metadata field in the SDK), then aggregate downstream.

A minimal Admin API filter for option 1:

curl https://api.anthropic.com/v1/organizations/usage_report/messages \
  -H "x-api-key: $ANTHROPIC_ADMIN_KEY" \
  -H "anthropic-version: 2023-06-01" \
  --data-urlencode "starting_at=2026-05-01T00:00:00Z" \
  --data-urlencode "ending_at=2026-05-09T00:00:00Z" \
  --data-urlencode "group_by[]=workspace_id" \
  --data-urlencode "group_by[]=model"
Enter fullscreen mode Exit fullscreen mode

The response groups token spend by workspace, so apps_api and packages_ui show up as separate rows. For deeper coverage of the Admin API endpoints — including budget alerts and per-workspace rate limits — see our Anthropic Admin API tutorial. Aggregate the JSON in claudecosts.app, a self-hosted Grafana board, or even a daily Google Sheet pull. Tag the report with the package name in your dashboard and you can finally answer "is apps/web worth what we are spending on it?"

For option 2 (single workspace, header tagging), the SDK call looks like this:

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  metadata: { user_id: `pkg:${process.env.PACKAGE_NAME}` },
  messages: [{ role: "user", content: "..." }],
});
Enter fullscreen mode Exit fullscreen mode

Set PACKAGE_NAME in each package's .env (or read from package.json#name) and your usage report can group by metadata.user_id prefix. This is lighter weight than spinning up multiple workspaces, but the reporting is only as good as your tagging discipline — one untagged call and the cost lands in "unknown."

Common pitfalls (5)

  1. Root CLAUDE.md too large. Over 100 lines means it is doing per-package work. Litmus test: if a section starts with "in packages/foo," it belongs in packages/foo/CLAUDE.md.
  2. Cross-package edits Claude misses. A type change in packages/types rippling to four consumers is the most common silent failure — types tests pass, four consumers break. Mitigate with a pre-commit hook running tsc --noEmit repo-wide, or a turbo run typecheck step. See our Claude Code hooks deep dive for hook patterns.
  3. Unattended Claude Code in CI/CD. Never give a CI runner full repo write permission — no human in the loop to catch a misfire. Use the Agent SDK with an explicit tool list and path deny-list, or scope to a single package via working directory.
  4. Stale CLAUDE.md. Renames invalidate every CLAUDE.md mentioning the old path. Add a CI check that greps each CLAUDE.md for paths that no longer exist. Stale guidance is worse than none — the model follows it confidently.
  5. Subagent context bleed. A scoped subagent still sees the parent's conversation history unless you set the system prompt to ignore it and pass an explicit working directory at spawn time. Verify by asking the subagent "what files are you allowed to edit?"

For the foundations of Claude Code itself — slash commands, permissions, model routing — start with our complete Claude Code guide.

Frequently Asked Questions

Should every package have its own CLAUDE.md?

No. Apply the rule "if a package needs more than two sentences of context, give it a CLAUDE.md." Leaf packages with three files and zero special rules can rely on the root file. We typically see 60-70% of packages get their own file in production monorepos.

How do I prevent Claude from editing other packages?

Three layers, weakest to strongest. (1) Tell it not to in the per-package CLAUDE.md — works most of the time. (2) Use a directory-scoped subagent — the agent definition restricts the working directory. (3) Add a pre-commit hook that rejects diffs touching files outside the package the session was launched in. Layer 3 is the only one that gives you a hard guarantee.

Cost tracking by package?

Easiest path: one Anthropic workspace per top-level package, then use the Admin API usage_report/messages endpoint with group_by=workspace_id. If you cannot create that many workspaces, tag each request with a metadata.package field via the SDK and aggregate in your own dashboard. Both approaches are documented in the Admin API tutorial linked above.

Does Turborepo cache Claude outputs?

It can, but it should not. Claude responses are non-deterministic — the same inputs hash will produce different outputs across runs, so a cache hit would silently replay stale generation. Always set "cache": false on tasks that invoke Claude. Turbo can still cache the consumers of those outputs (e.g. the build task that reads a Claude-generated file), and inputs should include both the source files and the generated artifact so changes invalidate correctly.

What about microservices repos?

A microservices repo is a monorepo with deployment boundaries — every pattern here applies. Extra rule: each service's CLAUDE.md should forbid editing deployment manifests (Dockerfile, fly.toml, k8s YAML) without a human in the loop. A "helpful" tweak to a memory limit can take prod down. Keep manifests in a single infra/ directory marked read-only, and pair with a hook that rejects writes from non-infra sessions.


Claude API Cost Optimization Masterclass ($59) — Per-workspace cost control is one chapter. The full playbook covers prompt caching, model tiering, Batch API routing, and the spreadsheet framework to forecast and cap monthly Claude spend before it surprises you.

Last verified May 2026 against Turborepo 2.x, pnpm 9, Nx 19, and Bun 1.x. Patterns drawn from monorepos in the 5-30 package range; very large repos (100+ packages) need additional graph-based context selection that we will cover in a follow-up.

Top comments (0)