12.1 Claude Code Architecture & Project Configuration
Key Takeaways
- CLAUDE.md files from the working directory and every parent are concatenated in root-to-cwd order rather than overriding each other, and CLAUDE.local.md is appended after CLAUDE.md at each level.
- Settings precedence runs managed settings, then --settings, then .claude/settings.local.json, then .claude/settings.json, then ~/.claude/settings.json, and list keys such as permissions.allow merge across scopes instead of replacing.
- Tool permissions are configured only through the permissions object with allow, ask, and deny rule arrays; allowedTools, confirmTools, and blockedCommands are not settings.json keys.
- There is no .claudeignore file; files are hidden from the agent with permissions.deny rules such as Read(./.env), which apply immediately without waiting for workspace trust.
- CLAUDE.md is context rather than enforcement, so unconditional restrictions belong in permissions.deny or a PreToolUse hook, and each file should stay under about 200 lines to preserve adherence.
Claude Code Architecture & Project Configuration
Exam Blueprint Focus: Claude Code is only 3.1% of CCDV-F on its own, but Configuration Management (4.1%) lives inside the much larger Applications and Integration domain and is graded on the same files. You need the real
CLAUDE.mdload order, the realsettings.jsonschema and precedence chain, and the real mechanism for hiding files from the agent — not a plausible-sounding one.
What is Claude Code?
Claude Code is Anthropic's agentic command-line interface for software engineering. Unlike a chat window or an inline autocomplete plugin, it runs in the developer's terminal with tools that touch the real filesystem, the real shell, and the real git repository. It is available as a CLI, a desktop app, a web app, and IDE extensions for VS Code and JetBrains.
Claude Code runs on current frontier Claude models — the Claude 5 family (Claude Opus 5, Claude Sonnet 5) and Claude Haiku 4.5 — and the model is selectable per session. Do not memorize a specific model as "the Claude Code model"; memorize that it is configurable through the model settings key and the /model command, and that an organization can constrain the choice with a managed availableModels allowlist.
+-------------------------------------------------------------------------+
| Developer Terminal Session |
| $ claude "Refactor the authentication middleware to use JWT tokens" |
+------------------------------------+------------------------------------+
v
+------------------------------------+------------------------------------+
| Claude Code Agent Runtime |
| 1. Loads memory: managed -> user -> project -> local -> subdirectory |
| 2. Resolves settings: managed > --settings > local > project > user |
| 3. Applies permissions.allow / .ask / .deny to every tool call |
+------------------------------------+------------------------------------+
v
+------------------------------------+------------------------------------+
| Claude API (current model) |
| - Adaptive thinking, steered by effort |
| - Emits tool calls: Read, Glob, Grep, Edit, Write, Bash, Agent, WebFetch|
+------------------------------------+------------------------------------+
v
+------------------------------------+------------------------------------+
| Local Environment Execution |
| Filesystem edits | git inspection | shell: pytest, npm test, linters |
+------------------------------------+------------------------------------+
v
| Agent loop: read failures -> repair -> re-run -> confirm |
+-------------------------------------------------------------------------+
Core capabilities
- Shell integration. Runs build tools, package managers, migrations, and test suites natively.
- Repository awareness. Reads git status, branch history, and diffs, and writes commit messages from them.
- Multi-file navigation.
Globfinds files by path pattern;Grepsearches content;Readopens them. - Closed-loop verification. Runs tests, parses failures, self-corrects, and iterates rather than assuming its own output compiles.
- Delegation. Spawns subagents for isolated, context-bounded sub-tasks.
Project Memory: CLAUDE.md
A fresh session starts with an empty context window. CLAUDE.md is the file you write to carry durable project knowledge across every session: build commands, conventions, architecture, and hard prohibitions.
What belongs in CLAUDE.md
Treat it as the place you write down what you would otherwise re-explain. Add an entry when Claude makes the same mistake twice, when a code review catches something Claude should have known, or when a new teammate would need the same context.
- Build, lint, and test commands — the exact invocation, so the agent does not guess
npm testwhen the project needspnpm test:unit --runInBand. - Architecture and layout — the design paradigm and where things live.
- Code style and language conventions — typing strictness, error-handling patterns, naming.
- Operational guardrails — "never edit files under
prisma/migrations", "never modify.env", "never push tomain".
Size discipline is a real constraint. Target under 200 lines per CLAUDE.md. The file is loaded into context at the start of every session, so a bloated document spends tokens on every turn and measurably reduces adherence. Claude Code loads a CLAUDE.md up to 4 MiB and skips a larger one — but long before that limit, longer means worse.
CLAUDE.md is context, not enforcement. It is delivered as a user message after the system prompt. Claude reads it and tries to follow it; there is no compliance guarantee. To make something unconditional, use a PreToolUse hook or a permissions.deny rule instead. This distinction is exam-relevant: "how do I stop Claude from ever running X?" is never answered by CLAUDE.md.
Production example
# Project Guidelines: Billing Service
## Repository Architecture
- Event-driven microservice: Fastify (TypeScript) + PostgreSQL.
- `src/domain` pure entities; `src/application` use cases; `src/infrastructure` adapters.
## Common Commands
- Install: `pnpm install --frozen-lockfile`
- Unit tests: `pnpm test:unit`
- Single file: `pnpm test:unit tests/unit/services/invoice.test.ts`
- Lint: `pnpm lint && pnpm prettier --check .`
## Coding Standards
- TypeScript strict mode. Never `any`; use `unknown` with type guards.
- Currency in integer cents. Never floating-point arithmetic on money.
- Expected failures return `Result<T, E>`; throw only at the HTTP boundary.
## Prohibited Actions
- Do NOT edit existing migrations; generate a new one with `pnpm db:generate`.
- Do NOT install packages without explicit permission.
- Never modify or commit `.env` files.
Where CLAUDE.md files live, and how they load
| Scope | Location | Shared with |
|---|---|---|
| Managed policy | /Library/Application Support/ClaudeCode/CLAUDE.md (macOS), /etc/claude-code/CLAUDE.md (Linux/WSL) | Everyone in the organization; cannot be excluded |
| User | ~/.claude/CLAUDE.md | Just you, all projects |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md | The team, via version control |
| Local | ./CLAUDE.local.md | Just you, this project (gitignore it) |
The critical mechanic — and the one most candidates get wrong — is that discovered files are concatenated, not overridden. Claude Code loads CLAUDE.md and CLAUDE.local.md from the working directory and every directory above it, ordered from the filesystem root down to where you launched. So foo/CLAUDE.md appears in context before foo/bar/CLAUDE.md, and instructions closest to your launch directory are read last. Within a directory, CLAUDE.local.md is appended after CLAUDE.md.
There is no override table and no "specificity wins" rule. If two files contradict each other, Claude may pick either one arbitrarily — which is why reviewing for conflicting instructions is a real maintenance task, not a nicety.
Files in subdirectories below the working directory are not loaded at launch. They are pulled in on demand when Claude reads files in those directories, which is what makes monorepos workable.
Imports and rules
CLAUDE.md can import other files with @path/to/file syntax. Imports expand at launch, resolve relative to the importing file, and can nest recursively to a maximum depth of four hops. Import parsing skips code spans, so writing `@README` in backticks keeps it literal.
Importing does not save context — imported files load into the window just the same. For instructions that should load only sometimes, use .claude/rules/: markdown files with optional paths: frontmatter that scope a rule to matching files.
---
paths:
- "src/api/**/*.ts"
---
# API Development Rules
- All endpoints must validate input.
- Use the standard error response format.
Rules without a paths field load at launch with the same priority as .claude/CLAUDE.md. For task-specific procedures that should not sit in context at all, use a Skill instead.
Claude Code reads CLAUDE.md, not AGENTS.md. A repository that already uses AGENTS.md should add a CLAUDE.md containing @AGENTS.md.
Configuration Management: settings.json
CLAUDE.md guides the model. settings.json configures the runtime, and the runtime enforces it regardless of what Claude decides.
The precedence chain
Claude Code reads settings from several files. When the same key is set in more than one, this order decides, highest first:
- Managed settings —
managed-settings.json, MDM policy, or the claude.ai console. Nothing you set overrides these. - Command line —
claude --settings ..., for this session. - Project local —
.claude/settings.local.json, yours, this project (gitignored). - Shared project —
.claude/settings.json, committed for the team. - User —
~/.claude/settings.json, yours, every project.
Note the direction: project settings outrank your personal user settings, and both are outranked by anything your organization manages. One important nuance: list-valued keys such as permissions.allow are merged across files rather than replaced, so each scope can add entries without deleting another's.
A realistic settings.json
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"model": "claude-sonnet-5",
"env": {
"NODE_ENV": "test",
"CI": "true"
},
"permissions": {
"allow": [
"Bash(pnpm test:*)",
"Bash(pnpm lint)",
"Read(src/**)"
],
"ask": [
"Bash(git push:*)"
],
"deny": [
"Read(./.env)",
"Read(./secrets/**)",
"Bash(curl:*)"
],
"defaultMode": "acceptEdits"
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh" }]
}
]
}
}
Key fields:
model— the model this project starts sessions with.env— environment variables injected into the tool execution context.permissions— an object withallow,ask, anddenyarrays plusdefaultModeandadditionalDirectories. Each entry is a rule of the formToolorTool(specifier).hooks— keyed by event name (PreToolUse,PostToolUse,SessionStart, …), each holding matcher groups.
High-yield trap:
allowedTools,confirmTools, andblockedCommandsare notsettings.jsonkeys. Permissions are configured exclusively through thepermissionsobject withallow,ask, anddeny. An answer choice built on the older flat key names is wrong.
Hiding files from the agent
There is no .claudeignore file. Restricting what Claude Code can read is a permissions concern, expressed as deny rules against the Read tool:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./**/*.pem)",
"Read(./credentials.json)",
"Read(./coverage/**)"
]
}
}
deny rules apply immediately and do not wait for workspace trust, which is exactly the property you want for secret files. This matters for three reasons the exam cares about: context exhaustion (vendored trees and build output flooding the window), cost and latency (megabytes of minified output billed as input tokens), and credential exposure (a .env read into the prompt).
Comparison Matrix
| Dimension | CLAUDE.md | .claude/rules/*.md | .claude/settings.json |
|---|---|---|---|
| Role | Persistent project instructions | Topic- or path-scoped instructions | Runtime configuration and enforcement |
| Format | Markdown | Markdown + optional paths: frontmatter | JSON |
| Consumer | The model, as context | The model, as context | The Claude Code runtime |
| Loaded | Every session, concatenated up the tree | At launch, or when a matching file is read | At session start; hot-reloaded on change |
| Enforcement | Advisory | Advisory | Binding |
| VCS | Commit | Commit | Commit; keep settings.local.json gitignored |
Common Traps and Operational Pitfalls
- The kitchen-sink CLAUDE.md. Hundreds of lines of pasted code and API docs. It loads on every session, spends tokens on every turn, and reduces adherence. Target under 200 lines; move procedures into Skills and path-scoped work into
.claude/rules/. - Expecting override semantics. Ancestor and local
CLAUDE.mdfiles are concatenated, not merged with precedence. Contradictions are resolved arbitrarily, so remove them rather than relying on the "closest" file winning. - Using CLAUDE.md as a security control. "Never run
rm -rf" in markdown is a suggestion.permissions.denyand aPreToolUsehook are enforcement. - Committing secrets in project settings. Personal API keys belong in
~/.claude/settings.jsonor the environment, never in the committed.claude/settings.json. - Inventing
.claudeignore. It does not exist. Usepermissions.denywithRead(...)rules. - Assuming imports save context.
@pathimports load into the window at launch exactly like inline text. Only Skills and path-scoped rules defer loading.
In a monorepo, an engineer starts Claude Code in ./packages/payment-service. The repository root CLAUDE.md says "run tests with pnpm test"; ./packages/payment-service/CLAUDE.md says "run tests with pnpm test:payments". How does Claude Code actually handle this?
A security engineer must guarantee that Claude Code can never read ./.env in a shared repository, regardless of what the model decides to do. Which configuration achieves this?
A team's committed .claude/settings.json allows Bash(pnpm test:*). An individual engineer adds Bash(pnpm lint) to permissions.allow in their own .claude/settings.local.json. What is the resulting effective allow list for that engineer, and why?