Codex in a Monorepo: Workflows That Scale
Running Codex against a monorepo is a different problem than running it against a single-package repo. A codex monorepo workflow has to solve context scoping (which package is this change actually about), command discovery (there are twelve different build tools, not one), and blast radius (a change in a shared package can silently break four unrelated apps). Get those three things right and Codex becomes a real force multiplier across a large repo. Get them wrong and you spend more time reviewing bad diffs than you would have spent writing the code yourself.
This guide walks through a working setup for using Codex, OpenAI's coding agent CLI, inside a monorepo: how to structure AGENTS.md files per package, how to scope a session so it does not wander into unrelated code, how to wire it into CI, and the failure modes that show up once your repo passes a few hundred thousand lines.
Why monorepos break naive Codex usage
A single AGENTS.md at the repo root works fine for a small project. In a monorepo it falls apart fast. If you tell Codex "run the tests" at the root, it has to guess which test runner applies: Jest for the web app, pytest for the data pipeline, go test for the internal service. If you tell it "add a field to the User type," it has to figure out whether that means editing a Prisma schema, a protobuf definition, or three different TypeScript interfaces that have drifted out of sync (which, in most real monorepos, they have).
The fix is not a smarter prompt. It is repo structure that a coding agent can actually navigate the same way a new hire would: scoped instructions, a package boundary the agent respects, and commands that are discoverable rather than memorized.
Nested AGENTS.md files
Codex reads AGENTS.md files and merges them, with more specific files overriding general ones. In a monorepo this is the single highest-leverage thing to set up correctly. The pattern:
/AGENTS.md # repo-wide conventions
/apps/web/AGENTS.md # Next.js app specifics
/apps/api/AGENTS.md # backend service specifics
/packages/ui/AGENTS.md # shared component library
/packages/db/AGENTS.md # schema and migration rulesThe root file should stay short and stay generic. It is read on every single session regardless of what the task touches, so bloating it with package-specific detail wastes context on every run and increases the odds Codex applies the wrong convention to the wrong package.
Root AGENTS.md, kept intentionally thin:
## Repo layout
- apps/web: customer-facing Next.js app
- apps/api: Node/Express backend, talks to Postgres via Prisma
- packages/ui: shared React components, published internally
- packages/db: Prisma schema, migrations, seed scripts
- packages/config: eslint, tsconfig, tailwind config shared across packages
## Universal rules
- Never edit generated files (anything under */generated/ or *.gen.ts)
- Run `pnpm changeset` for any change under packages/* before opening a PR
- Commit messages: conventional commits (feat:, fix:, chore:, refactor:)
- Do not add new top-level dependencies without checking package.json in the
affected workspace first; most are already in a shared packageThen packages/db/AGENTS.md carries the detail that only matters when Codex is actually working in that package:
## Database package rules
- Schema lives in schema.prisma, migrations are generated, never hand-written
- After any schema.prisma change, run: pnpm prisma migrate dev --name <name>
- Never run migrate deploy locally, that is CI/production only
- Seed data changes go in seed.ts, guarded by NODE_ENV checks
- Foreign key changes require updating apps/api/src/types manually, this
repo does not have full type generation across the boundary yetThat last line matters more than it looks. Codex cannot infer an undocumented manual step like a cross-package type sync that used to be automatic and stopped being automatic eighteen months ago. Every monorepo has two or three of these load-bearing manual steps. Write them down or watch Codex ship a broken PR because it did the "obviously correct" thing that happens to be wrong for your repo.
Scoping a session to one package
The other half of the problem is keeping Codex inside the package boundary during a single task. Two things help here.
First, cd into the workspace before starting the session instead of running Codex from the repo root:
cd apps/api
codex "add rate limiting middleware to the /webhooks route"Codex still has read access to the rest of the repo (it will walk up to find AGENTS.md and can read imports), but its working directory context and the AGENTS.md merge order both bias it toward apps/api conventions. This alone eliminates a large share of "why did it touch the web app" surprises.
Second, be explicit about the blast radius in the prompt itself when a change is genuinely package-local:
codex "add rate limiting middleware to the /webhooks route in apps/api.
Do not modify packages/ui or apps/web. If you find you need a change
outside apps/api, stop and tell me instead of making it."This costs one extra sentence and saves a round of "please revert the changes to packages/ui you weren't asked to make." Codex will comply with an explicit boundary far more reliably than it will infer one from repo structure alone.
The shared-package problem
The genuinely hard case in a codex monorepo workflow is a change to a shared package: packages/ui, packages/db, an internal SDK, anything with more than one consumer. A naive agent session fixes the immediate bug in the shared package and stops, unaware that three other apps import that function and one of them depends on the exact behavior being changed.
Two practical mitigations:
Consumer manifest in the shared package's AGENTS.md. List who imports what, so the agent at least knows to check:
## Consumers of this package
- apps/web imports: Button, Modal, DataTable, useToast
- apps/api imports: nothing directly, but apps/api/src/emails uses
packages/ui/templates for transactional email HTML
- apps/mobile imports: Button, Modal (via react-native alias, NOT the
same Button component, see packages/ui/native/)
Before changing a public export, grep for its usage across apps/* and
list every call site in your summary before making the change.Ask for an impact summary before the diff. Structure the prompt as two steps instead of one:
codex "I want to change the signature of formatCurrency in packages/ui/src/format.ts
to accept a locale parameter. First, find every call site across the monorepo
and list them with the current call signature. Do not make any changes yet."Review that list. Then follow up in the same session:
codex "Now make the change, and update every call site you listed to pass
a locale, defaulting to 'en-US' where the caller doesn't already have one."Splitting discovery from mutation is the single biggest reliability gain for shared-package edits. It also gives you, the reviewer, a natural checkpoint: if the call-site list looks wrong or incomplete, you catch it before any code changes exist, not after.
Build and test command discovery
Monorepos usually run on a task runner: Turborepo, Nx, Bazel, or a hand-rolled set of pnpm/yarn workspace scripts. Codex works better when the exact invocation is spelled out rather than left for it to guess from package.json.
In the root AGENTS.md:
## Common commands
- Install: pnpm install (never npm install, this repo uses pnpm workspaces)
- Run one package's tests: pnpm --filter <package-name> test
- Run one package's tests, watch mode: pnpm --filter <package-name> test --watch
- Typecheck everything: pnpm turbo run typecheck
- Typecheck one package: pnpm --filter <package-name> typecheck
- Lint: pnpm turbo run lint
- Build affected only: pnpm turbo run build --filter=...[origin/main]That last command, building only what changed relative to origin/main, is worth calling out explicitly for Codex. Left to its own judgment it will often run a full repo build to "be safe," which on a large monorepo can take ten or twenty minutes per iteration. Telling it the affected-only invocation up front keeps the feedback loop fast enough that it will actually run tests between edits instead of batching everything into one untested diff.
Wiring Codex into CI for a monorepo
The CI use case is different from the interactive one: instead of a developer scoping a session by hand, a workflow needs to compute the scope automatically from the diff. The general shape, using GitHub Actions and Turborepo's affected-package detection:
name: codex-review
on:
pull_request:
types: [opened, synchronize]
jobs:
scope:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.affected.outputs.packages }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Compute affected packages
id: affected
run: |
PACKAGES=$(pnpm turbo run build --filter=...[origin/main] --dry=json \
| jq -c '[.tasks[].package] | unique')
echo "packages=$PACKAGES" >> "$GITHUB_OUTPUT"
codex-review:
needs: scope
runs-on: ubuntu-latest
if: needs.scope.outputs.packages != '[]'
steps:
- uses: actions/checkout@v4
- name: Run Codex review on affected packages only
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
codex exec "Review the changes in this PR limited to these
packages: ${{ needs.scope.outputs.packages }}. Check each
package's AGENTS.md for local conventions before reviewing.
Flag anything that breaks a consumer listed in a shared
package's AGENTS.md. Post findings as a PR comment, do not
make code changes." --full-auto=falseThe point of computing packages first is the same principle as the interactive workflow: narrow the context before asking the agent to reason about it. A CI job that hands Codex the full repo on every PR is slow, expensive, and noisy, since it will happily comment on code the PR never touched. A job that hands it exactly the affected packages, plus their local AGENTS.md files, produces review comments that are actually about the change in front of the reviewer.
Keep the CI job in review-only mode (no auto-commit, no auto-merge) until you have run it against real PRs for a few weeks. Once you trust the false-positive rate, a natural next step is a narrower auto-fix job scoped to mechanical issues only, lint violations, missing changesets, import ordering, where the diff is low-risk enough to apply without a human gate.
Handling drift between package conventions
Large monorepos accumulate inconsistency. The web app might use named exports, the API might use default exports for the same kind of module. One package tests with Jest, another migrated to Vitest last quarter and never finished. This is not a Codex problem to fix; it is a repo problem that Codex will faithfully replicate unless you tell it otherwise.
Two options, both legitimate depending on your team's appetite for a cleanup project:
- Document the inconsistency explicitly in each package's AGENTS.md ("this package uses Vitest, packages/ui still uses Jest, do not convert either without a separate migration task") so Codex matches local convention rather than importing a pattern from whichever package it looked at last.
- If you are mid-migration, add a line pointing at the target state: "new test files should use Vitest even though older files in this package use Jest, do not touch existing Jest files as part of unrelated changes."
Either way, the fix is the same instinct as everything else in this workflow: write down what a human reviewer already knows implicitly, because that implicit knowledge is exactly what an agent working package by package does not have.
A checklist for setting this up
- Root AGENTS.md stays short: repo map, universal rules, common commands only
- Every workspace with its own build/test tooling gets its own AGENTS.md
- Shared packages list their consumers and require a call-site check before public API changes
- Prompts for shared-package work split into a discovery step and a mutation step
- Commands in AGENTS.md use the affected-only / filtered invocation, not the full-repo one
- CI scopes Codex to the packages touched by the diff, not the whole repo
- Package-level convention drift is documented, not left for Codex to guess or homogenize
FAQ
Does Codex read every AGENTS.md file in a large monorepo on every session? No. It reads the AGENTS.md in the working directory and walks up toward the repo root, merging what it finds along the path, with the most specific file taking precedence on conflicting instructions. It does not eagerly read AGENTS.md files in sibling packages unless a prompt or an import chain leads it there. That is exactly why cd-ing into the right workspace before starting a session matters.
Should the root AGENTS.md list every package, even ones a given task will never touch? A short one-line-per-package map is fine and cheap. What you want to avoid is putting each package's detailed conventions in the root file, since that content gets loaded on every session regardless of relevance. Keep detail in the nested files where it is only loaded when relevant.
How do I stop Codex from "helpfully" fixing unrelated code it notices while working in a package? State the boundary explicitly in the prompt, as shown above, and reinforce it in that package's AGENTS.md with a line like "flag unrelated issues instead of fixing them inline." Codex generally respects an explicit boundary; the wandering behavior shows up mostly when no boundary was stated at all.
Is Turborepo or Nx required to make this work? No, the pattern works with any monorepo tool, or with plain pnpm/yarn workspaces and hand-written scripts. What matters is that the affected-package computation exists somewhere, whether that is turbo run build --filter=...[origin/main], an Nx affected command, or a custom script that diffs against the base branch. Codex just needs that list handed to it rather than being asked to compute it by reading the whole repo itself.
What is the biggest mistake teams make rolling this out? Starting with a single repo-root AGENTS.md and one broad "review my PR" CI job across the entire monorepo. Both work fine on a small repo and both degrade badly past a certain size, producing either generic advice that ignores package-specific convention or a flood of comments on code nobody touched. Scoping early, even before it feels necessary, is cheaper than retrofitting it once a team has learned to ignore noisy Codex output.
AI CodingShip full-stack AI apps at conversation speed — specs, agents, deploys, all from the terminal.
CodexLearn to drive OpenAI's coding agent: real tasks, safe sandboxing, and terminal-to-cloud workflows that ship.