Skip to content

xtask setup-agents symlinks CLAUDE/AGENTS.md -> READMEs#10628

Open
david-crespo wants to merge 3 commits into
mainfrom
add-claude-md
Open

xtask setup-agents symlinks CLAUDE/AGENTS.md -> READMEs#10628
david-crespo wants to merge 3 commits into
mainfrom
add-claude-md

Conversation

@david-crespo

@david-crespo david-crespo commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

The original description below is still relevant because I am still adding a CLAUDE.md and we are still depending on how Claude Code pulls in CLAUDE.md files in subdirectories, but this point of this PR is no longer to add CLAUDE.md files. We decided we'd rather keep useful docs in README files, so in order to take advantage of CC's machinery, we gitignore CLAUDE.md/AGENTS.md below top level and add an xtask which creates symlinks that point at any readmes in the tree so that CC automatically pulls them in when working in that subdirectory. We do both CLAUDE.md and AGENTS.md because that covers all agent harnesses in wide use (everybody but CC accepts AGENTS.md).

See https://code.claude.com/docs/en/memory#how-claude-md-files-load.

Claude docs excerpt and test of CLAUDE.md loading behavior

Claude Code reads CLAUDE.md files by walking up the directory tree from your current working directory, checking each directory along the way for CLAUDE.md and CLAUDE.local.md files. This means if you run Claude Code in foo/bar/, it loads instructions from foo/bar/CLAUDE.md, foo/CLAUDE.md, and any CLAUDE.local.md files alongside them.

All discovered files are concatenated into context rather than overriding each other. Across the directory tree, content is ordered from the filesystem root down to your working directory. For the foo/bar/ example, foo/CLAUDE.md appears in context before foo/bar/CLAUDE.md, so instructions closer to where you launched Claude are read last. Within each directory, CLAUDE.local.md is appended after CLAUDE.md, so your personal notes are the last thing Claude reads at that level.

Claude also discovers CLAUDE.md and CLAUDE.local.md files in subdirectories under your current working directory. Instead of loading them at launch, they are included when Claude reads files in those subdirectories.

This leaves ambiguous whether the walking-up behavior also applies when reading files lower down the tree, but I had Claude test it:

Read(/Users/david/oxide/omicron/ztest_nest/a/b/c/target.txt)
  Loaded ztest_nest/a/CLAUDE.md
  Loaded ztest_nest/a/b/CLAUDE.md
  Loaded ztest_nest/a/b/c/CLAUDE.md

Empirically confirmed: behavior (b). Reading ztest_nest/a/b/c/target.txt injected all three — ZALPHA, ZBRAVO, and ZCHARLIE — i.e. the CLAUDE.md from every intermediate directory along the path from cwd down to the file's own directory, not just the file's immediate dir.
...
On-demand subtree loading walks the intermediate directories. When Claude reads nexus/db-queries/src/db/datastore.rs, it pulls in nexus/CLAUDE.md, nexus/db-queries/CLAUDE.md, and nexus/db-queries/src/db/CLAUDE.md (any that exist) — together with the root and ancestor files already loaded at launch.

This means sprinkling CLAUDE.md files around the tree should work pretty well.

Output of cargo xtask setup-agents --dry-run
$ cargo xtask setup-agents --dry-run
    Finished [`dev` profile [unoptimized + debuginfo]](https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles) target(s) in 0.32s
     Running `target/debug/xtask setup-agents --dry-run`
would link CLAUDE.md, AGENTS.md -> README:
  bootstore/ (README.adoc)
  dev-tools/crdb-seed/ (README.md)
  dev-tools/ls-apis/ (README.adoc)
  end-to-end-tests/ (README.adoc)
  gateway/ (README.adoc)
  illumos-utils/src/ (README.adoc)
  illumos-utils/src/fakes/ (README.adoc)
  live-tests/ (README.adoc)
  nexus/db-queries/benches/ (README.adoc)
  nexus/db-queries/src/db/ (README.adoc)
  nexus/reconfigurator/ (README.md)
  nexus/tests/integration_tests/data/ (README.md)
  nexus/tests/static/ (README.md)
  nexus-config/ (README.adoc)
  oximeter/ (README.md)
  oximeter/db/benches/ (README.md)
  oximeter/db/schema/ (README.md)
  schema/crdb/ (README.adoc)
  schema/crdb/one-big-ereport-table/ (README.adoc)
  schema/crdb/vpc-firewall-icmp/ (README.adoc)
  sled-agent/ (README.adoc)
  sled-hardware/src/ (README.adoc)
  smf/sled-agent/gimlet/ (README.md)
  smf/sled-agent/gimlet-standalone/ (README.md)
  smf/sled-agent/non-gimlet/ (README.md)
  smf/switch_zone_setup/ (README.md)
  sp-sim/ (README.adoc)
  support-bundle-collection/ (README.md)
  tools/ (README.adoc)
  uuid-kinds/ (README.adoc)
  wicket/ (README.md)

setup-agents (dry run):
    31  would link
     0  unchanged
     0  orphaned (would remove)
     0  hand-authored dirs left alone
🤖 How various harnesses handle nested CLAUDE.md/AGENTS.md
tool reads at root nested subtree load inline @import
Claude Code CLAUDE.md yes — on file read, anywhere in the tree yes (@path, eager)
Amp AGENTS.mdAGENT.mdCLAUDE.md yes — on file read, anywhere in the tree yes (@path, globs)
Codex AGENTS.md only no¹ no (literal text)
opencode AGENTS.md (→ CLAUDE.md fallback) no² no (literal)
Pi AGENTS.md or CLAUDE.md no — root + parent walk at startup no (literal)

¹ Codex concatenates AGENTS.md along the git-root→cwd path (root-first, leaf-last), so a nested file loads only if you launch Codex inside that subdirectory. Launched from the repo root — our normal case — it reads just the root AGENTS.md.

² opencode walks up from cwd, so it never auto-loads nested files either. Its instructions glob (["**/AGENTS.md"]) could force-load them, but eagerly and unconditionally — all ~31 every session — so it's not worth committing.

So only Claude Code and Amp do true on-read, scoped subtree loading. For everyone else the per-directory symlinks are inert at a root launch, which is why the root AGENTS.md carries the model-directed nudge to read the relevant directory README on demand — the one portable mechanism that reaches all five.

Original description

We have lots of great docs in this repo, but we could use some brief guidance for robots about which docs to look at when working on what. @sunshowers has already established a great pattern by writing RFDs and docs and then adding skills that point at those. I don't want to change this pattern. The problem with skills is that, by design, they are loaded only if the user invokes them or the model decide they are relevant based on the description in the frontmatter. But there is guidance that is always relevant when working on a given crate, and that's what CLAUDE.md is for. The skill descriptions are always in context, so it is not necessary to list skills in CLAUDE.md files.

I held off on adding these before because the repo is huge and I didn't know how nested CLAUDE.md files work. We definitely don't want a giant top-level CLAUDE.md that's meant to apply to everything. Fortunately, CLAUDE.md files in subdirectories work more or less exactly how you would want (see the docs excerpt below): /<root>/a/CLAUDE.md gets loaded automatically whenever CC reads any file nested inside /<root>/a/.

My goal here is to add something very basic and unobjectionable to get us started, and then people can feel free to add more as they work. The top-level CLAUDE.md makes clear that we intend to add more and that we should try to keep them relatively short and general.

I also added a CLAUDE.local.md to .gitignore so people can use it for personal guidelines they don't want to check in. I had a CLAUDE.md that I was ignoring secretly with .git/info/exclude — if anyone else was doing something similar, copy that file to CLAUDE.local.md and change your ignore line to that.

@david-crespo david-crespo requested a review from sunshowers June 16, 2026 17:21
Comment thread nexus/CLAUDE.md Outdated

## Tips

- Typecheck with `cargo check -p omicron-nexus` (add `--all-targets` for tests).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not cargo xtask clippy?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW the top level README.adoc in omicron advises the use of nextest, cargo check, running clippy, rustfmt, etc.

@david-crespo david-crespo Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put that in the top one, but yeah.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth a mention of a few key things here unless we intend for it to read the full README every time. Which is plausible, it's only about 4k tokens and we could get rid of a bunch of redundant stuff in here. I'll work on that.

Comment thread schema/CLAUDE.md Outdated
- Use the `crdb-change` skill to generate schema changes and migrations; it
keeps `dbinit.sql` and the migration in sync.
- Every schema change needs both a `dbinit.sql` edit and a migration; the two
must produce the same result.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe something about how some migrations require an expectorate run to generate the migration verification files? or is this already in the crdb-change skill?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is already in the skill ("Step 8: Generate verification files")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread CLAUDE.md Outdated
@@ -0,0 +1,54 @@
# Omicron

Omicron is the Oxide control plane. It's a large Cargo workspace (~185 crates).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Omicron is the Oxide control plane. It's a large Cargo workspace (~185 crates).
Omicron is the Oxide control plane. It's a large Cargo workspace

(This seems prone to get out-of-date, and not really necessary for claude to load)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, very silly.

Comment thread nexus/CLAUDE.md Outdated
@@ -0,0 +1,28 @@
# omicron-nexus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on renaming these files to AGENTS.md and putting @AGENTS.md in CLAUDE.md? Or symlinking it. Just want to make sure anyone using a different agent also gets these instructions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely could do it, my worry is that that would have to apply to all the nested ones too, or I guess we could include instructions in the top level one to help other agents with different logic?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd do it across the repo. (I've used this pattern in nextest and it works quite well.)

@david-crespo

Copy link
Copy Markdown
Contributor Author

After discussion in the control plane channel, it seems like we want to get everything in README files (thanks @iliana), and then add an xtask setup-agents that adds gitignored symlinks to those readme files from CLAUDE.md (and probably AGENTS.md) right next to all the READMEs in the tree (thanks @emilyalbini). We can still have a top-level CLAUDE.md and AGENTS.md that only exists to make sure agents know that they have to run xtask setup-agents if they want to see everything. This allows us to hook into automatic guaranteed loading logic in agent harnesses without sequestering useful info in CLAUDE.md files.

@david-crespo david-crespo changed the title Add some very basic CLAUDE.md files xtask setup-agents symlinks CLAUDE/AGENTS.md -> READMEs Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants