CLAUDE.md is your project’s onboarding doc
Every time Claude Code starts in your repository, it automatically reads a file called CLAUDE.md from the project root. Think of it as the onboarding document you wish every new hire read — except this one actually gets read, every session.
Generate a first draft by running the /init command in your project. Claude inspects the build files and layout and writes a starting CLAUDE.md. Then edit it — the value is in the project-specific knowledge that isn’t obvious from the code.
A good CLAUDE.md for a Java service is short and high-signal:
# Acme Orders Service
Spring Boot 3.2 service (Java 21). Hexagonal architecture:
domain in `core/`, adapters in `adapters/`, wiring in `app/`.
## Build & test
- Build: `./gradlew build`
- Unit tests only: `./gradlew test`
- Integration tests (Testcontainers, needs Docker): `./gradlew integrationTest`
- Run locally: `./gradlew bootRun` (profile `local`, needs Postgres on 5432)
## Conventions
- Constructor injection only — never field injection.
- Tests use JUnit 5 + AssertJ. No Hamcrest, no `assertEquals`.
- Persistence via JPA; every schema change needs a Liquibase changeset
in `src/main/resources/db/changelog/`.
- Public API DTOs live in `adapters/web/dto` and are never reused as
domain objects.
## Gotchas
- `OrderMapper` is MapStruct-generated; edit the interface, not the
generated class.
- Integration tests will not run without Docker Desktop running.
Why this works: it tells Claude the exact commands to run, the conventions that aren’t enforced by the compiler, and the traps that would otherwise cost a wasted round-trip. Notice there’s nothing here that Claude could read from the code itself — don’t restate the package structure; capture the things a newcomer gets wrong.
Keep it lean.
CLAUDE.mdis loaded into context every session, so every line costs tokens on every turn. Ruthlessly cut anything generic (“write clean code”) or anything Claude can discover by reading a file. Aim for a screenful, not a wiki.
Memory at three levels
You can place memory files at different scopes:
- Project —
./CLAUDE.md, committed to the repo and shared with your team. This is the main one. - User —
~/.claude/CLAUDE.md, your personal preferences across all projects (e.g. “I prefer Gradle Kotlin DSL,” “explain before large refactors”). - Subdirectory — a
CLAUDE.mdinside a module is picked up when Claude works in that part of the tree. Useful in large multi-module builds where one module has unusual rules. (In a reactor build, also tell Claude the precise per-module test command —mvn -pl billing testor./gradlew :billing:test— so it doesn’t run the whole build to check one module.)
To capture a convention mid-session, just tell Claude “add that rule to CLAUDE.md,” or open the file and edit it directly — it’s a normal file in your repo. The point is that the knowledge becomes durable instead of living only in one conversation.
Permissions: let the safe things run, gate the rest
By default Claude asks before running a command or editing a file. That’s the right default, but for a Java project you run the same handful of commands constantly, and approving ./gradlew test for the hundredth time is friction with no safety benefit.
You control this with permission rules. The quickest path: when Claude asks to run a command you trust, choose the “always allow” option. To manage rules directly, use /permissions, or edit settings (below). Allow-list the things that are safe and reversible:
{
"permissions": {
"allow": [
"Bash(./gradlew test:*)",
"Bash(./gradlew build:*)",
"Bash(mvn test:*)",
"Bash(mvn compile:*)"
],
"ask": [
"Bash(./gradlew publish:*)"
],
"deny": [
"Bash(rm:*)"
]
}
}
A note on matching: a pattern like Bash(mvn test:*) matches mvn test followed by any arguments, so mvn test -Dtest=OrderTest is covered too; without the :* it must match exactly. Get this wrong and you’ll wonder why ./gradlew test --info still prompts when Bash(./gradlew test) is allowed.
The principle: allow read-only and locally-reversible commands (compiling, testing, formatting), ask for anything that reaches the outside world (publishing artifacts, pushing, deploying), and deny the destructive. Treat the deny-list as defense-in-depth, not a security boundary — Bash(rm:*) won’t stop find … -delete or git clean -fdx. The real protection is that you read every diff and every command before approving it.
Permission modes
Press Shift+Tab to cycle modes for the whole session:
- Default — ask before edits and commands.
- Accept edits — auto-accept file edits so a long, well-scoped task doesn’t stop on every file. Use it when you trust the plan and intend to review the final diff.
- Plan mode — read-only; Claude can explore and plan but cannot change anything (covered in Module 2).
(A fully unattended bypass mode also exists but is off by default and deliberately hard to enable — you almost never want it on a real codebase.)
The settings.json hierarchy
Configuration lives in JSON files, applied in layers:
~/.claude/settings.json— your user settings, all projects..claude/settings.json— project settings, committed and shared with the team. Put your agreed build-command allow-list here..claude/settings.local.json— your personal project overrides, git-ignored. Machine-specific paths or experiments go here.
A practical split for a team repo: commit a .claude/settings.json that allow-lists mvn test, ./gradlew build, and the formatter, so every developer gets a low-friction setup out of the box, and keep anything personal in .local.json. Make sure the local file — and anything holding credentials — is git-ignored:
# .gitignore
.claude/settings.local.json
.mcp.json # if it contains tokens; otherwise commit it
This matters more once you wire up MCP servers in Module 7, because those configs can carry access tokens you must never commit.
Tip: Commit
.claude/settings.jsonand yourCLAUDE.md. They are project knowledge, exactly like your.editorconfigor CI config — versioned, reviewed, and improved over time. When a session reveals a missing convention or a recurring gotcha, add it toCLAUDE.mdin the same PR.
What just happened
You turned a generic agent into one that knows your build commands, your conventions, and your traps — and you set guardrails so routine commands run freely while risky ones still stop for a human. With this in place, the everyday work in the next module gets much smoother.
Key takeaways
CLAUDE.mdis auto-loaded project memory: capture exact build/test commands, unenforced conventions, and gotchas — not what the code already shows.- Keep
CLAUDE.mdlean; it costs context on every turn. Generate a draft with/init, then prune. - Use permission rules to allow safe/reversible commands, ask for outward-facing ones, deny the destructive.
- Settings layer: user → project (committed) → local (git-ignored). Commit shared knowledge so the whole team benefits.