An agent fixing a route in packages/api runs npm test from the root, because that is the command
the root AGENTS.md names. It waits four minutes for the web package’s browser tests to fail on an
unrelated snapshot. The api package has its own test command, in a file the agent never opened.
Which file an agent reads, and which wins when two disagree, depends on the tool you opened.
What you get
You will end up with a per-package layout that four tools honor and a resolver that prints which files each tool loads from one directory. The root file says which rule wins. This is for you if one repository holds packages with different commands.
Short answer
Put a package’s commands and conventions in packages/<name>/AGENTS.md and keep the root file to
what applies everywhere, with one line saying the package file wins where they disagree. Codex
reads the chain from the root down to its working directory. Cursor and Copilot give precedence to
the file nearest the one being edited, and Claude Code loads the file in a subdirectory when it
opens something there. Path-scoped rules cover the same ground without nesting.
You will need
Node 22 or later, a monorepo with at least two packages, and an agent that reads AGENTS.md.
Verified 2026-10-21 against Node 22.22.0. The loading rules the sample encodes come from the
documentation of Codex,
Claude Code, Cursor, and
Copilot.
The sample runs no agent. It reads two fixture repositories and reports what each tool would load,
as its documentation describes.
Approaches compared
| Approach | When it fits | What it costs you | When to pick something else |
|---|---|---|---|
Claude Code rules with paths | Claude Code teams who want rules per file pattern rather than per directory | A .claude/rules directory one tool reads, loaded only when Claude opens a matching file, so a rule about commands can arrive after the first command | The repository is used from several agents, where AGENTS.md reaches all of them |
Copilot instructions with applyTo | Copilot code review and the cloud agent, where the request names the files | Files under .github/instructions that other agents ignore, and a glob to keep in step with the package layout | The agent is a terminal tool working from a directory rather than from a request |
Cursor .mdc rules with globs | Cursor users who want a rule attached whenever a package’s file is in context | One more format in .cursor/rules, read by one editor, with globs that stop matching after a directory move | Your team is not on Cursor, or the same text already sits in a nested AGENTS.md |
| Nested AGENTS.md files | Several agents on one repository, with a directory per package | A file per package to keep current, and loading rules that differ by tool, since Codex stops at its working directory | One package, where the root file is the package file |
Nesting and path scoping solve the same problem from two ends. A nested AGENTS.md is found by
where the agent is or what it opens, and every tool here reads one, though not in the same way. A
path-scoped rule is found by matching the file being edited against a glob, and each tool reads
only its own format. Codex is the outlier: it builds its chain from the working directory, so a
package below that directory never loads.
Give each package its own file, and make the root defer
The package file carries what differs: the commands, the layout, and the conventions the package keeps that its neighbors do not.
# packages/api
The HTTP API, a Node 22 service with no framework.
- Test: `npm run test --workspace packages/api`
- Lint: `npm run lint --workspace packages/api`
- Routes live in `src/routes/`, one file per resource. Register a new route in `src/router.mjs`.
- Every handler validates its body against the schema in `src/schemas/` before touching the store.
The root file keeps what holds everywhere, and says in words what happens when the two disagree.
# Monorepo
Two npm workspaces, `packages/api` and `packages/web`, sharing one lockfile. Each package has its
own `AGENTS.md` with its test command and conventions. Where this file and a package file disagree,
the package file wins.
- Install: `npm ci`
- Test: the command in the package's `AGENTS.md`; `npm test` from the root runs every workspace
- Commit messages follow Conventional Commits.
- Do not edit `package-lock.json` by hand.
That sentence about disagreement is there because the tools give you three different answers without it. Codex concatenates the chain from the root down and says a closer file overrides earlier guidance because it appears later in the prompt, which is an order, not a rule.1 Cursor says more specific instructions take precedence. Claude Code says that when two instructions contradict each other it may pick one arbitrarily. One line in the root file turns all three into the same answer.
Mirror the package file where a tool wants its own format
Three of the four tools also read a path-scoped rule in a format of their own, and the body of each
is the package file’s bullet list. Cursor attaches an .mdc rule when a file matching its globs
is in context.
---
globs: packages/api/**
alwaysApply: false
---
Copilot adds an .instructions.md file to a request when a file it names in applyTo is part of
that request.
---
applyTo: "packages/api/**"
---
Claude Code loads a rule under .claude/rules when Claude reads, writes, or edits a file matching
its paths, and a rule with no paths at launch, with the same standing as the project file.
---
paths:
- "packages/api/**"
---
Four copies of one list is a maintenance problem, so generate the three rule files from the
AGENTS.md rather than editing them by hand. The glob is the part that drifts: a package renamed
or moved keeps its AGENTS.md, because the file moves with it, and loses every rule whose pattern
still names the old path.
Ask each tool what it loads, from inside one package
The resolver encodes one loading rule per tool, taken from its documentation. Codex walks from the root to the working directory; Claude Code reads the working directory and its parents at launch, and the file in a subdirectory once it opens something there.
export const tools = {
// Codex builds the chain once, from the project root down to the working
// directory, one AGENTS.md per directory, and stops there.
codex: ({ root, cwd }) => chain(cwd).map((d) => join(d, 'AGENTS.md')).filter((rel) => exists(root, rel)),
// Claude Code reads AGENTS.md from the working directory and above when no
// CLAUDE.md is there, a subdirectory's AGENTS.md once it opens a file there,
// and a .claude/rules file whose paths match the file or that has no paths.
claude: ({ root, cwd, file }) => {
const launch = chain(cwd).map((d) => join(d, 'AGENTS.md')).filter((rel) => exists(root, rel))
const below = relative(cwd, dirname(file)).startsWith('..') ? [] : chain(dirname(file)).slice(chain(cwd).length)
const onDemand = below.map((d) => join(d, 'AGENTS.md')).filter((rel) => exists(root, rel))
return [...launch, ...onDemand, ...ruleFiles(root, '.claude/rules', '.md', 'paths', file, { unscoped: true })]
},
The demo runs two repositories through it: repo-mono, whose root file names npm test, and
repo-fixed, whose root defers. For each it starts the agent inside packages/api to edit a
handler, then at the root to edit a web component.
node which.mjs
repo-mono, started in packages/api, editing packages/api/src/handler.mjs
codex loads AGENTS.md, packages/api/AGENTS.md
test commands: npm test | npm run test --workspace packages/api
claude loads AGENTS.md, packages/api/AGENTS.md, .claude/rules/api.md
test commands: npm test | npm run test --workspace packages/api
cursor loads AGENTS.md, packages/api/AGENTS.md, .cursor/rules/api.mdc
test commands: npm test | npm run test --workspace packages/api
copilot loads .github/copilot-instructions.md, .github/instructions/api.instructions.md, packages/api/AGENTS.md
test commands: npm test | npm run test --workspace packages/api
Every tool loads the package file, and every tool also sees the root’s npm test, so each one
holds two commands and resolves the pair by its own rule. The agent that opened this page’s problem
was reading the same two lines. The fixed repository removes the choice.
node which.mjs
repo-fixed, started in the root, editing packages/web/src/app.mjs
codex loads AGENTS.md
test commands: none
claude loads AGENTS.md, packages/web/AGENTS.md, .claude/rules/web.md
test command: npm run test --workspace packages/web
cursor loads AGENTS.md, packages/web/AGENTS.md, .cursor/rules/web.mdc
test command: npm run test --workspace packages/web
copilot loads .github/copilot-instructions.md, .github/instructions/web.instructions.md, packages/web/AGENTS.md
test command: npm run test --workspace packages/web
Started inside packages/api, all four tools see one command in the fixed repository. Started at
the root and editing the web package, three of them reach packages/web/AGENTS.md through the file
being edited, and Codex does not, because its chain ends at the working directory. The real check
is the one each tool’s documentation offers: ask the agent, before it runs anything, which
instruction files are active and which test command it will use. Codex documents
codex --cd packages/api --ask-for-approval never "Show which instruction files are active.",
Claude Code lists its memory files under /context, and Copilot names the instructions file in a
response’s references.
Check it worked
The fourth test pins the Codex behavior, which is the one most likely to surprise a team that starts every session at the root.
test('started at the root, Codex never reaches a package file below the working directory', () => {
assert.deepEqual(resolve('codex', { root: 'repo-fixed', ...atRoot }), ['AGENTS.md'])
assert.deepEqual(testCommands('repo-fixed', resolve('codex', { root: 'repo-fixed', ...atRoot })), [])
})
node --test resolve.test.mjs
1..8
# tests 8
# suites 0
# pass 8
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 109.406805
Four others assert that every tool loads packages/api/AGENTS.md from inside packages/api, and
that the contradicting repository shows 2 commands to every tool where the fixed one shows 1.
Two more check that a file at the root lists each file once and that a Claude Code rule with no
paths loads for any file. The last checks that a rule scoped to one package never loads for a
file in the other.
When it goes wrong
The agent runs the root’s command from inside a package. Both files name a test command and the root says nothing about which wins, so the answer depends on the tool. Codex goes by position in the prompt, Cursor by specificity, and Claude Code by whichever it picks. Take the command out of the root file and state the precedence there, as the fixed repository does.
Codex ignores the package file. The session started at the root, and Codex builds its chain from
the root down to the working directory only, so a file below it is never read. Start Codex in the
package directory, or pass --cd packages/api, which its documentation offers for exactly this.
Claude Code ignores every AGENTS.md in the repository. Someone added a CLAUDE.md or a
CLAUDE.local.md in the working directory or a parent of it, and by default Claude reads
AGENTS.md only when none of those exists.2 Set the project instructions option to read both files, or
import AGENTS.md from the CLAUDE.md.
When not to do this
Do not nest for a repository with one package. The root file is the package file, and a second copy of it one directory down is a second thing to keep current.
Do not copy the root’s rules into each package file so that the package file stands alone. Every tool here loads the root as well, so the agent reads both copies on every turn. The day the copies differ, you have the contradiction this page is about, with the precedence line pointing at the wrong one.
Do not move a rule that must hold everywhere into the package files. Codex started at the root will
not read them, and Claude Code reads the file in a subdirectory only after it opens something
there. A rule about commits or secrets belongs in the root file, with a copy in
.github/copilot-instructions.md, because Copilot gives the nearest AGENTS.md precedence over
the root one.
Do not treat the resolver as the agent. It encodes what four documentation pages say, on the day they were read, and the tools change faster than this page will. The listing command of the tool in front of you is the authority.3
Related how-tos
Last verified
Verified 2026-10-21 against Node 22.22.0. Every output block is what the command preceding it printed. No coding agent was run: the resolver encodes each tool’s loading rule as its documentation states it, and the two fixture repositories are read from disk.
Footnotes
-
The Codex instructions page includes at most one file per directory, preferring
AGENTS.override.mdtoAGENTS.md, and joins the chain from the root down. It stops adding files once the combined size reachesproject_doc_max_bytes, 32 KiB by default. The chain is read in the order it was built. So when a root file is long enough to exhaust the budget, the file left out is the one nearest the work. That is the file the same page says should override the rest. ↩︎ Back to text -
The Claude Code memory page lists what counts for that check: a
CLAUDE.md, a.claude/CLAUDE.md, or aCLAUDE.local.mdin the working directory or any parent directory. A personal, uncommittedCLAUDE.local.mdtherefore switches off the sharedAGENTS.mdfor the one person who created it, which is a difficult bug to report to the colleagues who cannot see it. ↩︎ Back to text -
Copilot’s documentation says the nearest
AGENTS.mdin the directory tree takes precedence, and Cursor’s rules page says nested files are combined with their parents with more specific instructions taking precedence. Nearest and more specific describe the same arrangement from two directions. Neither page says what happens when the nearer file is the less specific one. ↩︎ Back to text