A CLAUDE.md file in a project root gives Claude Code persistent context about that codebase — conventions, commands to run, things to avoid — without you re-explaining them at the start of every session. It is read automatically at session start; the discipline is keeping it accurate and current, since stale guidance in it is worse than no guidance at all (Claude will confidently follow instructions that no longer match the codebase). Treat it like any other reviewed file: update it in the same commit that changes the convention it documents.
The CLAUDE.md hierarchy
CLAUDE.md is not a single file — it resolves across a hierarchy, and knowing which level to put something in matters. From broadest to narrowest: an enterprise managed policy file (deployed by an organization's IT/security team, outside any individual repo, and not overridable by project or user files); a project-level CLAUDE.md at the repo root, checked into version control and shared with the whole team (build commands, architectural conventions, style rules everyone needs); and a user-level CLAUDE.md at ~/.claude/CLAUDE.md, which applies across every project for that individual and is where personal preferences belong (e.g. "prefer concise diffs" or an individual's tooling shortcuts) — it should never carry team-wide conventions, since teammates never see it. CLAUDE.md files also support @path/to/file imports, letting a root file pull in more detailed docs (e.g. a testing guide) without bloating the main file that loads on every session.
Key concept
Match the scope of the guidance to the scope of the file: team-wide, repo-specific conventions go in the project CLAUDE.md that gets committed; individual preferences that would be noise to teammates belong in the user-level file instead. Mixing the two is a common source of a bloated, half-relevant project file.
The permission system: allow, ask, deny
Claude Code gates tool use through a permission system configured in settings.json, not through CLAUDE.md prose. Rules are written as Tool(pattern) strings — for example Bash(git commit:*), Bash(rm:*), or Edit — and each rule is placed in one of three lists: allow (run without prompting), ask (prompt the user before running), or deny (block outright, no prompt). Settings themselves live at multiple scopes — a checked-in .claude/settings.json shared with the team, a gitignored .claude/settings.local.json for an individual's personal overrides, a user-level ~/.claude/settings.json, and an enterprise-managed settings file the organization controls — and higher scopes generally take precedence over lower ones, with enterprise managed policy always winning. But the rule that actually matters for the exam is orthogonal to scope: within permission evaluation, a matching deny rule always wins over a matching allow rule, regardless of which file each came from. You cannot allow-list your way past a deny.
Common exam distractor
A scenario states a command matches both an allow rule in one settings file and a deny rule in another, then asks what happens. The trap answer picks whichever file has narrower/closer scope, or whichever was "added most recently." The correct rule is simpler and file-independent: deny beats allow whenever both match, full stop.
Slash commands and starting fresh
Claude Code ships built-in slash commands for session control — /clear wipes context and starts a genuinely fresh session, /compact summarizes and shrinks the current conversation instead of discarding it, /permissions and /mcp inspect the permission and MCP configuration currently in effect, and /agents manages configured subagents. Beyond the built-ins, custom slash commands package a repeated prompt or workflow into a short, memorable invocation: a Markdown file under .claude/commands/ (project-scoped, shared) or ~/.claude/commands/ (personal) becomes a command named after the file, its body is the prompt template, and $ARGUMENTS in that body is substituted with whatever text follows the command when invoked. Frontmatter on the file can restrict which tools the command is allowed to use and describe it for /help. Subdirectories namespace commands (a file at .claude/commands/git/commit.md becomes /git:commit).
Just as important day to day as building commands is knowing when not to reuse a session: /clear starts fresh when you are switching to an unrelated task, rather than letting stale context from the last task bleed into the next one's reasoning (the same principle Lesson 3.4 covers for API-level session management). /compact is the right tool instead when the task is still the same one but the conversation has grown long and is approaching the context window — you want to keep the thread of work, just condensed.
Hooks: deterministic control at tool boundaries
CLAUDE.md and prompts are probabilistic guidance — Claude usually follows them, but "usually" is not a guarantee. When a rule must hold every time (block a dangerous command outright, log every file edit, enforce a lint pass before a commit succeeds), Claude Code's hooks configured in settings.json are the deterministic mechanism, mirroring the same hooks-versus-prompts tradeoff from the Agent SDK (Lesson 1.5) but applied to the CLI tool itself. Hooks bind a shell command to a lifecycle event — PreToolUse fires before a tool runs and can block it, PostToolUse fires after and can react to the result, UserPromptSubmit fires when the user submits a prompt, and Stop fires when Claude finishes responding — optionally filtered to specific tools via a matcher pattern. The hook script receives event details as JSON on stdin; exit code 0 lets execution continue normally, while exit code 2 blocks the action and feeds the hook's stderr back to Claude as the reason, so Claude can adjust and retry rather than the operation silently failing.
MCP server configuration
External tools and data sources are wired in through the Model Context Protocol. A project-scoped .mcp.json at the repo root, checked into version control, gives every teammate the same configured servers automatically when they open the project. The claude mcp add CLI command registers a server at a chosen scope — local (this machine only, not shared), project (writes to .mcp.json), or user (available across all of that person's projects) — and /mcp inside a session lists configured servers along with their connection and authentication status, which is the first place to check when a tool a server should provide is not showing up.