Claude Code Configuration and Workflows
How to configure Claude Code so conventions load for the right files at the right cost, surface skills only when they should, and keep CI runs deterministic while preserving interactive control.
Claude Code is not a single prompt engine but a layered configuration system where four memory locations, path-scoped rules, skill frontmatter, plan mode controls, feedback techniques, and headless flags compose to determine what Claude sees and what it is allowed to do. The exam treats this as the highest weight domain because every other domain's work happens inside this shell. If the wrong CLAUDE.md loads, or a skill clutters the main conversation, or a CI job hangs waiting for input, no downstream prompt or tool fix can recover the session. Understanding the shell is therefore the first order decision.
The domain's six tasks map directly to the six ways the shell fails under pressure. Task 3.1 draws the memory hierarchy and the concatenation versus override rule that decides which team member gets which instruction. Task 3.2 unifies the two legacy slash command paths into the current Skills system and shows when isolation is required. Task 3.3 makes conditional loading token efficient so file-type conventions do not tax unrelated work. Task 3.4 draws the line between planning and acting based on ambiguity, not difficulty. Task 3.5 supplies the feedback order that fixes interpretation without wasting turns. Task 3.6 moves the same system into headless CI where interactive defaults become blocking defects.
Two principles cut across every task. First, always loaded versus on demand is the structuring choice. CLAUDE.md and path-scoped rules are always loaded or conditionally always loaded for matching files, while skills are on demand and only pay context when invoked. Putting on demand procedures in always loaded memory leaks tokens, and putting always loaded standards in on demand skills makes them invisible when needed. Second, instructions are probabilistic while settings are enforced. CLAUDE.md can guide the model but cannot guarantee a boundary, while settings.json permission rules and hook decisions enforce regardless of what Claude decides. Every exam trap that asks which layer guarantees ordering, blocking, or approval tests that distinction.
CLAUDE.md Hierarchy and Scoping
Place conventions at the correct scope so they concatenate into every session that needs them and never leak into sessions that do not.
What you need to know
Claude Code reads configuration from four kinds of locations, each with a distinct scope and sharing property. User level at ~/.claude/CLAUDE.md applies to every project on the machine and is never version controlled. Project level at CLAUDE.md or .claude/CLAUDE.md at the repo root is shared via git to every clone. Directory level CLAUDE.md inside any subdirectory applies only to work in that subtree. CLAUDE.local.md is the companion that loads immediately after CLAUDE.md at the same level and carries personal overrides that remain gitignored. The exam repeatedly asks which file applies in a given scenario and the discriminator is rarely file name but always file scope.
The most heavily tested property is that these layers concatenate rather than override. All discovered CLAUDE.md files are loaded into context ordered from filesystem root down to the working directory, so the file closest to where Claude was launched is read last and carries the last word if instructions conflict, but nothing earlier is discarded. Two files that contradict still both sit in context and the model may pick either. That is why the docs note that contradictory rules may be resolved arbitrarily. Treating depth as shadowing is the most common wrong answer, and the exam correct answer is that CLAUDE.md is guidance, not configuration, and rules that must hold belong in settings.json or hooks.
The @path/to/file import directive is the composition mechanism that keeps a single CLAUDE.md maintainable as a project grows. Past a few hundred lines the file becomes unmanageable and shared standards such as linting rules are referenced from multiple package files. The syntax is @ followed by a path written anywhere in the body with no separate @import keyword, relative paths resolve against the importing file, recursive imports stop at four hops, and parsing skips Markdown code spans and fenced code blocks so an @path shown inside backticks is mentioned literally without triggering expansion. Both root and per package files can import the same shared file, so the rule exists once and is referenced from many sites without duplication.
Two companion mechanisms complete the hierarchy. Path-scoped rules in .claude/rules/ carry YAML frontmatter with a paths array of globs and load only when Claude reads a matching file, so they are the correct answer for file type conventions scattered across many directories. The claudeMdExcludes array in .claude/settings.json takes glob patterns against the CLAUDE.md file path itself and skips discovery of files that would otherwise surface through the walk up chain or lazy subdirectory discovery in a noisy monorepo. There is no allow ask deny override at the CLAUDE.md level, permission rules live in settings.json because CLAUDE.md is instructions, not enforcement.
Three verification and gating details are tested as cause and effect pairs. /memory lists locations Claude Code knows about and is the guide keyed answer for which files are known, while /context reports which files actually loaded into the running session under Memory files, and neither command triggers loading, they only diagnose what is already loaded. The --add-dir flag makes an extra directory reachable to tools but does not load that directory's CLAUDE.md unless the environment variable CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD is set to 1. When the Agent SDK embeds Claude Code, no filesystem settings load by default and an agent that ignores project CLAUDE.md almost always has settingSources not including project, with systemPrompt preset claude_code keeping the standard prompt intact when embedding.
The four locations and what they share
User level at ~/.claude/CLAUDE.md is personal, outside any repo, and never reaches teammates through git. Project level at CLAUDE.md or .claude/CLAUDE.md at the root is the team standard that rides with the code. Directory level in packages/api/CLAUDE.md or packages/web/CLAUDE.md scopes package specific conventions such as Zod validation in the API versus Testing Library in the web app, and loads only when Claude reads a file in that package. CLAUDE.local.md sits beside its peer at the same level, loads last there, and is the gitignored place for a personal database path or a temporary debugging note.
The ordering rule is concatenation from broadest to most specific, with local appended last at its directory. The file closest to the launch directory is read last, so it has the final word if phrasing conflicts, but earlier files are not removed. When two rules truly conflict the model may choose arbitrarily, which is the exam's cue that the requirement belongs in a deterministic layer such as settings.json or a hook rather than in prose.
Import and exclusion with precise semantics
The @path/to/file directive is not a preprocessor include with an @import keyword but a literal @ followed by a path written inline in the CLAUDE.md body. Imported files are expanded and loaded into context at launch alongside the file that referenced them, relative paths resolve against the importing file not the working directory, recursion stops at four hops, and any @path inside backticks or a fenced code block is skipped so examples do not accidentally expand.
claudeMdExcludes in .claude/settings.json takes globs against CLAUDE.md file paths to skip, not against directories the file would have applied to, so a pattern like star star slash experimental slash star star excludes every CLAUDE.md under any experimental directory. This is the fix for noisy monorepos where another team's CLAUDE.md keeps surfacing through the walk up chain or lazy discovery.
Loading triggers and cost
The walk up chain loads eagerly at session start from the working directory up through every parent to the filesystem root, always contributing any CLAUDE.md found on that walk. Subdirectory CLAUDE.md files below the working directory load lazily, entering context only when Claude actually reads a file in that subdirectory, so a session working only in packages/web never pays the context cost of packages/payments/CLAUDE.md.
The same lazy trigger applies to path-scoped rules and to nested CLAUDE.md. When Claude reads a file, the directory file for that file's parent loads if not already present and any path-scoped rule whose glob matches loads. That incremental cost is why per package rules are cheap in a monorepo and why packing all conventions into root CLAUDE.md is wasteful when only one package's rules are needed at a time.
Gating for extra directories and SDK embedding
Extending the view with --add-dir makes the extra directory's code reachable to tools, but loading its CLAUDE.md requires the gate CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD set to 1. Without the gate the code is reachable and the instructions are not, which the exam presents as a one line cause and effect where the learner assumes the flag implies memory loading.
When Claude Code runs embedded via the Agent SDK, the caller selects which filesystem layers are honored through the settingSources selector on query options. By default no filesystem settings load, so an SDK agent that ignores project CLAUDE.md and skills almost always has settingSources not including project. The companion systemPrompt preset claude_code keeps the standard Claude Code system prompt intact when embedding, and omitting it explains missing safety guidance.
Authoritative mechanism reference
The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.
Mechanism reference
This section documents every mechanism in the hierarchy with full field names, ordering rules, value spaces, and boundary conditions. Each subsection traces ownership to the layer that actually enforces the guarantee.
Mechanism reference: 1. The CLAUDE.md tiers and what travels
Claude Code discovers CLAUDE.md in four documented locations plus enterprise delivery, each with a distinct scope, storage tier, and sharing contract.
| Tier | Canonical path | Lives outside repo | Committed | Reaches every clone | Loaded with |
|---|---|---|---|---|---|
| Managed policy | macOS: /Library/Application Support/ClaudeCode/CLAUDE.md or Linux/WSL: /etc/claude-code/CLAUDE.md or Windows: C:\Program Files\ClaudeCode\CLAUDE.md plus claudeMd key in managed-settings.json | Yes, machine image | Via MDM, Group Policy, Ansible, or managed-settings.json | All sessions on managed machines, cannot be excluded | Before user and project, same priority as managed file |
| User | ~/.claude/CLAUDE.md | Yes, home directory | Never | Only the owner, on any repository | At session start alongside project files |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md at repository root | No | Yes, via git | Every teammate after clone or pull | At session start |
| Local | ./CLAUDE.local.md alongside any CLAUDE.md | No, but gitignored | Convention says no, add to .gitignore | Only the owner for that checkout, not shared via git | Appended after sibling at same level |
| Directory | subdirectory/CLAUDE.md such as packages/api/CLAUDE.md, packages/web/CLAUDE.md | No | Yes | Every teammate | Lazily, only when Claude reads a file inside that subdirectory |
Two points that are tested by distractor phrasing deserve emphasis. First, both ./CLAUDE.md and ./.claude/CLAUDE.md count as project tier and both can be active simultaneously; a project with both has two active project files at once, not a conflict. Second, CLAUDE.local.md is not a fourth settings tier; it is memory with the same concatenation semantics and the same lack of guarantee as any other CLAUDE.md. Reading last does not confer precedence.
Secrets and sensitive paths illustrate the sharing boundary. The lessons state plainly not to put secrets in project CLAUDE.md because it is committed; API keys, passwords, and tokens belong in environment variables or in gitignored CLAUDE.local.md. The operational consequence is that any secret written to project memory leaks to every clone and to the model's context that is sent to the API, so the correct pattern is a reference such as STAGING_DB_URL via env, not a literal value in Markdown.
Size and token budget interact with tier choice. Files over roughly 200 lines consume proportionally more of the context window and reduce adherence; a checked-in file over 4 MiB is skipped entirely. The guide's remedy for a 600 to 900 line monolith is not a larger file but decomposition into .claude/rules/ with paths or into a skill that loads on demand, because those surfaces allow conditional loading while every tier of CLAUDE.md is always-loaded or lazily-always within its subtree.
Mechanism reference: 2. Load walk, concatenation, and order
The guides are consistent on three facts that together defeat every "which wins" distractor.
All discovered files are concatenated into context rather than overriding each other is stated verbatim. The walk collects CLAUDE.md and CLAUDE.local.md from the current working directory and every directory above it, ordering content from filesystem root down to working directory, so instructions closer to launch are read last. Within each directory, CLAUDE.local.md is appended after CLAUDE.md so personal notes are the last text at that level.
What this means operationally is that three files can be active at once for a developer working in packages/api under a repository that keeps both project locations: root CLAUDE.md, .claude/CLAUDE.md, and packages/api/CLAUDE.md all contribute text, with packages/api/CLAUDE.local.md appended after the package file if present.
No override, no silent discard, no recency tie-break. If two files contradict - single versus double quotes, 2 versus 4 space indentation, snake_case versus camelCase - both strings remain in context and if two rules contradict each other, Claude may pick one arbitrarily with there is no guarantee of strict compliance. The channel is a user message after the system prompt, which is probabilistic guidance, not a system instruction.
Subdirectory files and path-scoped rules are lazy. The walk-up chain loads at launch. Subdirectory CLAUDE.md under the working directory and any .claude/rules/*.md with paths frontmatter load only when Claude reads a file that the scope covers. A session that never touches packages/payments/ never pays for packages/payments/CLAUDE.md. This is the mechanism that makes per-package rules economical and explains why a file can appear to vanish after /compact and then reappear on the next matching read.
Block-level HTML comments <!-- maintainer notes --> are stripped before injection, which saves tokens; comments inside code blocks are preserved and remain visible when opening the file with Read.
Mechanism reference: 3. Example: layered memory layout for a monorepo
The first required substantial example shows a realistic monorepo with all tiers active, how the discovery walk concatenates, and where the "last word" actually sits versus where the guarantee sits. It is the connected layout that the later enforcement examples will reuse.
# File: ~/.claude/CLAUDE.md (user tier, personal, not committed)
Personal defaults
- Prefer verbose, descriptive variable names over short abbreviations.
- Always add JSDoc comments to exported functions.
- For TypeScript: strict mode, no
anytypes, explicit return types.
# File: /Library/Application Support/ClaudeCode/CLAUDE.md (managed, org policy) # Deployed via MDM, cannot be excluded via claudeMdExcludes
Organization standards
- Never log raw PII; use structured logger with redaction.
- All API handlers must validate input with Zod.
# File: ./CLAUDE.md (project tier, committed, shared)
Tech Stack
- Framework: Next.js 15 with App Router
- Styling: Tailwind CSS with shadcn/ui components
- Testing: Vitest + Testing Library
Commands
- Build:
npm run build - Test:
npm test - Lint:
npm run lint - Typecheck:
npm run typecheck
Conventions
- Components: one file per component in
src/components/ - API routes:
src/app/api/[route]/route.ts - State: Zustand stores in
src/stores/, not componentuseState
# File: ./.claude/CLAUDE.md (also project tier, committed)
Architecture notes
src/lib/api.tsis the only place that makes fetch calls.- Error handling: wrap with
AppErrorand includecode.
# File: ./packages/api/CLAUDE.md (directory tier, committed, lazy) # Loads only when Claude reads a file under packages/api/
API package
- Express.js with TypeScript, Zod validation on every endpoint.
- Routes in
packages/api/src/routes/, services inpackages/api/src/services/. - Endpoints require
authMiddleware; request and response schemas usecamelCasewitherror.detailsarray.
# File: ./packages/api/CLAUDE.local.md (local, gitignored, appended last at that level)
Local scratch
- Test account:
[email protected], staging DB atlocalhost:5433. - Temporary: verbose logging for this week's auth debugging.
How this loads for a developer who runs claude from packages/api and then opens a file under that package: text Load order (concatenated into one user message, root down to working dir, local appended after its sibling at each level): 1 Managed /Library/Application Support/ClaudeCode/CLAUDE.md 2 User ~/.claude/CLAUDE.md 3 Project ./CLAUDE.md 4 Project ./.claude/CLAUDE.md 5 Dir ./packages/api/CLAUDE.md (lazy: on first Read inside packages/api) 6 Local ./packages/api/CLAUDE.local.md (appended after dir file at that level) 7 Rules ./.claude/rules/*.md without paths (loaded at launch alongside .claude/CLAUDE.md) 8 Rules ./.claude/rules/*.md with paths (lazy: when a matching file is touched) Active set while editing inside packages/api: 1 plus 2 plus 3 plus 4 plus 5 plus 6 plus 7 plus any matching 8. Active set while editing inside packages/web: 1 plus 2 plus 3 plus 4 plus 7, sibling dir files stay unloaded.
What this example proves and where it fails. It proves that personal preferences stay personal via ~/.claude/CLAUDE.md or CLAUDE.local.md and that team conventions become durable only at project or directory tier via committed files. It proves that splitting a long project file into package files reduces cross-package noise only because directory files are lazy, not because concatenation was avoided. Its failure boundary is enforcement: if the project file says never write to config/ or always run prettier, that phrasing is still guidance that the model may miss on a busy turn; deterministic guarantees belong in settings.json or hooks, not in any tier of CLAUDE.md.
Local scratch: 4. @path import composition: eager, relative, bounded, and span-safe
Imports are the composition mechanism for CLAUDE.md. The guide's syntax is bare @path/to/file anywhere in prose such as See @README for project overview and @package.json for available npm commands and git workflow @docs/git-instructions.md.
Four rules govern the mechanism.
Relative resolution. Both relative and absolute paths are allowed, and relative paths resolve against the directory containing the importing CLAUDE.md, not the working directory where claude was launched. A file at .claude/CLAUDE.md that imports @./standards/naming.md therefore looks beside .claude/, not at repository root, which is the common source of "import exists but never loads" reports.
Eager inlining, same context cost. Imported files are expanded and loaded into context at launch alongside the containing file. Whether the text lives in one 600-line file or six 100-line files, the model sees the same total tokens; the split is an authoring win, not a token win. If the goal is to shrink per-session context, the tool is .claude/rules/ with paths or a skill.
Maximum depth of four hops. Imported files can recursively import others, up to a maximum depth of four hops; beyond that, further imports are silently not resolved. Deeply layered shared-config chains lose content past the bound without an error.
Code span and fence safety. Import parsing skips Markdown code spans and fenced code blocks, so @README inside backticks is literal text and is not imported, while bare @README in prose is imported. This is the boundary that distinguishes accidental mention from intentional import.
Silent skip on missing targets is a further boundary. A directive whose path does not exist is silently skipped and the session starts without that block. The practical consequence is that a typo or an uncommitted filename produces a silent gap that is visible only via /context or /memory.
Local scratch: 5. Example: import composition with depth and span safety
The second required example shows a publishable import graph that respects relative resolution, the four-hop bound, and code-span safety, and it evolves the layered layout into a maintainable standards tree.
# File: .claude/CLAUDE.md (project tier, imports resolve relative to .claude/)
# Coding standards
@./standards/naming-conventions.md
@./standards/error-handling.md
@./standards/testing-requirements.md
# Architecture notes (local to this file, not imported)
- src/lib/api.ts is the only place that makes fetch calls.
- Commit format [TICKET-ID] Brief description; use 4 space indentation.
# Reference material exposed via import (not via pasted prose)
See @README for project overview and @package.json for available npm commands.
Mention literal paths safely: use @docs/api-private.md in comments, not @docs/api-private.md.
# File: .claude/standards/testing-requirements.md (imported, committed)
Testing
- Run
npm testbefore committing; tests live in__tests__/adjacent to source. - Use
describeanditblocks, nottest()directly. - Imported by:
.claude/CLAUDE.mdhop 1 - Also imports: @./fixtures/shared-fixtures.md hop 2 (still within four-hop budget)
# File: .claude/standards/fixtures/shared-fixtures.md (hop 2)
Fixtures
- Fixture factory lives in
tests/fixtures/factory.ts; do not duplicate factories per package. - Hop chain: .claude/CLAUDE.md (0) -> standards/testing-requirements.md (1) -> fixtures/shared-fixtures.md (2)
- Any file that writes
@inside fences is safe:
# Example inside imported file - fenced block below is NOT an import
Here the author mentions @./should-not-import.md inside a code block.
# File: packages/api/CLAUDE.md (directory tier, selective import) # This package only needs API conventions, not frontend standards @../../.claude/standards/api-conventions.md
Package scope
- Routes in
packages/api/src/routes/, services inpackages/api/src/services/. - All endpoints require auth middleware and Zod validation.
Observable outcome. Running /context from repository root shows the project tier plus all three imported standards inlined as if their text lived inside .claude/CLAUDE.md; running from packages/api and reading a file inside that subtree also adds the directory tier and its selective import. Mentioning @README inside backticks or inside a fenced block does not create an extra Memory files entry. A fifth hop such as an import inside shared-fixtures.md that points to yet another file that itself imports further would be silently dropped once the depth budget is exceeded, which is visible as missing text in /context, not as an error.
Package scope: 6. .claude/rules/ and claudeMdExcludes
.claude/rules/*.md is the companion to directory CLAUDE.md for larger projects. Rules are discovered recursively, and a rule without paths frontmatter loads at launch with the same priority as .claude/CLAUDE.md; a rule with paths loads only when Claude works with files matching the specified patterns.
Path patterns use glob syntax. The guide documents single and multiple patterns plus brace expansion such as src/*/.{ts,tsx} and a shared expansion budget of 1,000 expanded patterns and 4 MiB per rule, with bracket expression rules like escaping literal [ as \[. Lessons emphasize that permission rules (allow, ask, deny) never belong in rules files; those arrays live in settings.json.
claudeMdExcludes is the discovery filter that removes noisy targets from the walk. It is an array of glob patterns matched against absolute file paths, set in any settings.json tier, merging across tiers, and its most consequential boundary is that managed policy files cannot be excluded.
// File: .claude/settings.local.json (local tier, gitignored, personal)
// Discovery filter: skip other teams' CLAUDE.md that leak into this walk
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"claudeMdExcludes": [
"**/monorepo/CLAUDE.md",
"/home/user/monorepo/other-team/.claude/rules/**",
"**/vendor/**",
"**/__generated__/**"
]
}The rule-level companion to walk-level exclusion is negation inside a path-scoped rule. The guide explicitly groups paths: ["/.ts", "/.tsx", "!/__generated__/", "!vendor/**"] as type glob with exclusions inside the rules tier, which is a distinct stage from walk exclusion, applied only after discovery, not instead of it.
--add-dir adds extra directory roots. By default their CLAUDE.md chains are not loaded; setting CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 opts into loading them alongside the primary working directory walk. Whether claudeMdExcludes also filters those added walks when the gate is enabled is the single point that remains not independently confirmed for lack of a combined example in documentation or lessons.
Package scope: 7. What survives compaction and what reloads
Block-level HTML comments aside, the compaction story is deliberately narrow. The conversation history is summarized and replaced, but CLAUDE.md at the project root is re-read from disk and re-injected after /compact because it was never part of the history to compress, so its instructions return intact. Nested CLAUDE.md files in subdirectories and rules with paths frontmatter do not return immediately; they reload the next time Claude reads a matching file rather than at the moment compaction ends.
Any instruction that only ever existed in conversation is free for the summariser to compress, which is why the durable fix for "vanished after compact" is to move the instruction into a tier that survives: project CLAUDE.md for universal, directory CLAUDE.md for subtree, or path-scoped rule for file-type scope. The InstructionsLoaded hook and /context are the two verification surfaces for whether a file did in fact reload in the current session.
Ownership map
Which layer owns which guarantee is the pivot for every diagnostic and every exam trap. Memory and settings are separate contracts with different enforcement positions.
Ownership map: Model and context delivery
The model owns probabilistic weighing of guidance that arrives as a user message. CLAUDE.md at any tier - managed, user, project, local, directory, and .claude/rules/ - is injected as context after the system prompt, before the conversation turns begin. The model decides how strongly to apply that guidance relative to existing code style and explicit user requests, with specificity and brevity increasing adherence. The client owns whether the text is present at all, which files are discovered, and in what order they are concatenated, but the model owns the per-token decision of whether to follow them on a given turn, and may pick arbitrarily when they conflict.
Ownership map: Claude Code client and CLI
The Claude Code client owns discovery, ordering, and lifecycle. It walks the filesystem from root down to working directory, appends CLAUDE.local.md after CLAUDE.md at each level, eagerly inlines @path imports respecting depth and code-span rules, applies claudeMdExcludes before concatenation, and gates --add-dir roots behind CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD. It reloads project-root CLAUDE.md after /compact, watches settings.json files for live reload, and exposes /memory as the file browser and auto-memory toggle and /context as the loaded-set reporter. Infrastructure such as MDM or Group Policy owns deployment of managed CLAUDE.md and managed-settings.json, which the client treats as non-excludable and top-ranked.
Ownership map: settings.json and permission evaluation
The client owns deterministic enforcement via settings.json. The permissions object with allow, ask, deny plus hooks is evaluated before the model acts, with deny evaluated first so a matching permissions.deny blocks even if another scope allows. The precedence stack is managed policy at the top, then --settings command-line JSON, then .claude/settings.local.json, then .claude/settings.json, then ~/.claude/settings.json at the bottom; lists merge instead of replacing for most array keys. Application code does not override these rules at runtime; it requests tools and the client decides.
Ownership map: Hooks
The client owns hook dispatch as a lifecycle gate. Events such as PreToolUse, PostToolUse, PostToolUseFailure, SessionStart, InstructionsLoaded, FileChanged, CwdChanged, Stop and roughly thirty others fire at fixed points, matching on tool name or literal filename, and invoking one of five handler types: command, http, mcp_tool, prompt, or agent. A PreToolUse hook can deny a tool before the model sees success, and a PostToolUse hook can rewrite tool_output via updatedToolOutput or inject additionalContext. Where multiple hooks conflict, the merge order is deny above defer above ask above allow. This position before or after the tool call is what makes hooks deterministic while memory is not.
Ownership map: Agent SDK
The Agent SDK owns which settings.json tiers an embedded agent sees via the settingSources selector, and which system prompt preset the session uses. The lesson and the forensics file agree that the SDK default loads no filesystem settings, so project CLAUDE.md, rules, and skills remain invisible unless the caller explicitly includes project and optionally user. Application code that embeds the SDK therefore owns the scope decision, while the client still owns enforcement once the scope is selected.
Ownership map: Summary table
| Guarantee | Owner | Why that layer |
|---|---|---|
CLAUDE.md text is present in context, ordered root down, local last | Claude Code client, filesystem walk | Walk and claudeMdExcludes gate happen before the model sees context |
| Import inlined, relative resolution, four hops, span-safe | Client | Expansion at load time, not model reasoning |
| "Use 2 space indent" is usually followed | Model, weighted by specificity and brevity | User-message guidance, no guarantee, may pick arbitrarily on conflict |
"Never run Bash(scp *)" is never executed | Client via settings.json permissions.deny deny-first evaluation | Evaluated before tool execution, cannot be overridden by memory |
"Run formatter after every Write" happens | Client via hooks PostToolUse | Fires at fixed lifecycle event regardless of model choice |
| SDK session sees project memory and skills | Application code via settingSources | Caller selects tiers; SDK honors that selection |
| Managed policy always applies, cannot be excluded or overridden | Infrastructure plus client | Managed files are non-excludable and top of precedence |
Version and terminology currency
Three vocabulary shifts matter for interpreting both documentation and exam distractors.
Managed versus enterprise versus organization-wide. The reference page phrases top-tier governance as managed policy sits above all memory tiers and cannot be overridden by project or user. Current guides use managed policy, managed settings, and managed-settings.json interchangeably, delivered via MDM, Group Policy, or server-managed settings, and describe claudeMd as the key that embeds managed CLAUDE.md content inside managed-settings.json itself. The exam guide's phrasing is therefore current, not legacy, and the lesson diagrams that show managed at the ceiling of both memory and settings remain the correct mental model.
settings.json hierarchy wording. Older discussion sometimes described settings as project, local, user in ambiguous order. The fetched settings guide resolves this unambiguously as managed above command line above project local above shared project above user, with the explicit note that command-line --settings JSON sits between managed and local. Lessons mirror this as managed or server-managed above settings.local.json above settings.json above ~/.claude/settings.json, with scalars overriding and permission arrays concatenating. The exam trap that swaps local and project in the chain is therefore stale and should be answered with the client precedence above.
Diagnostic command naming. The split between /memory and /context is the only terminology fork that is recent. Earlier Claude Code versions placed loaded-file reporting under /memory; current builds place it under /context under Memory files and reserve /memory for browsing, editing, and toggling auto memory. Lessons capture both surfaces and note the exam keys /memory while the keyboard shows /context. The exam tip on the reference page that On the exam, answer /memory is therefore version-accurate for keyed scoring; at the actual keyboard the proof step is /context.
Gate and key introductions. Two flags that appear as distractors precisely because older material omitted them are now fully documented. claudeMdExcludes is the documented walk-level filter with glob against absolute paths and cannot exclude managed files. CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 is the explicit opt-in to loading CLAUDE.md chains from --add-dir roots. A third set of enterprise keys - companyAnnouncements, disableBundledSkills, skillOverrides, availableModels, and $schema - gate what project files can do and surface validations in the editor when $schema points at https://json.schemastore.org/claude-code-settings.json, but do not change the memory hierarchy itself.
Import limits and span rules. The four-hop import depth and the code-span and fence skip are both current, not a recent tightening, and appear identically in the fetched guide and both lessons. Any material claiming unlimited depth or that backtick wrapping still imports is stale.
Official versus community divergence
Where community-written tutorials contradict the fetched guides, the guides control the keyed answer and the tutorial phrasing should be treated as a distractor.
| Community claim | Documentation position | What to answer and why |
|---|---|---|
More specific CLAUDE.md wins; user level overrides project level; inner file completely replaces root inside its directory | All discovered files are concatenated; if two rules contradict, Claude may pick arbitrarily; no tier has a guaranteed win; directory files supplement root rather than replace it | Answer concatenation and arbitrary pick, because memory is guidance delivered as a user message, not a precedence config. The guide's explicit quotes overrule the heuristic. |
Move occasionally skipped formatting or never modify config/ into stronger CLAUDE.md wording or .claude/rules/ with a glob to guarantee it | Guaranteed rules belong in settings.json or hooks; settings are enforced by the client regardless of model choice; permissions.deny with Bash(...) or PreToolUse blocks before execution | Answer settings.json or hooks, because memory is probabilistic and cannot promise determinism on every turn. |
Use @import ./file.md as the import keyword; use .mdc files with YAML x frontmatter and glob to scope Claude Code | Real syntax is bare @path/to/file with no keyword; real scoping is plain CLAUDE.md in subdirectories plus .claude/rules/*.md with paths frontmatter, not a .mdc format; import parsing skips code spans and fences | Answer @path and plain CLAUDE.md plus .claude/rules/ with paths; .mdc is a different tool's format used as a distractor. |
/memory triggers configuration loading or activates memory for teammates | Configuration loads automatically at session start by discovery; /memory and /context are diagnostic viewers that do not trigger loading | Answer that /memory lists and opens, /context reveals the loaded set; running either does not cause loading. |
Subdirectory CLAUDE.md files all load eagerly at launch alongside the walk-up chain | Subdirectory files under the working directory load lazily only when Claude reads a file in that subdirectory | Answer lazy loading; always-loaded versus lazy is the exam's cost and /compact differentiator. |
CLAUDE.md splitting via @ reduces context size because imported files are conditional | Imported files still load at launch and occupy the same tokens; to shrink context use path-scoped rules or skills | Answer that the split is organizational, not conditional, so the correct token remedy is .claude/rules/ or a skill. |
Pass --add-dir to automatically load extra CLAUDE.md chains | --add-dir makes extra directories reachable but does not load their memory unless CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 is set | Answer with the gate; without it, extra code is visible but extra instructions are not. |
A final divergence is the permissions syntax itself. Community examples sometimes scope built-in tools with a wildcard tool name. The lessons clarify that for built-in tools like Bash and Read, you scope with a parenthesized pattern such as Bash(npm test) or Read(./.env), with wildcard tool name only after a literal mcp__<server>__ prefix. The guide's settings example uses the same parenthesized form. The parenthesized pattern is the documented, testable form.
Beyond the task statement
The lessons cover several adjacent topics that the reference page omits entirely. Each matters for diagnosing hierarchy issues in production and for avoiding traps in sibling tasks.
.claude/rules/ with paths frontmatter and the rules-vs-skills boundary. The memory guide introduces --- YAML frontmatter with a paths array such as /.ts, src/api//.ts, or src/*/.{ts,tsx} that limits a rules file to sessions touching those files, and documents a shared budget of 1,000 expanded patterns plus 4 MiB per rule and bracket-escape rules like \[. The lessons connect this to the broader rule that permission arrays never belong in rules files and to the boundary that task-specific, conditional knowledge belongs in a skill that loads on demand rather than in always-loaded CLAUDE.md. For this task it matters because the reference page's correct fix for cross-directory type scoping and for 600 plus line bloat is not another directory CLAUDE.md but a path-scoped rule or a skill.
Auto memory as distinct from authored CLAUDE.md. The guide describes a separate auto-memory system that Claude writes itself at ~/.claude/projects/<project>/memory/ with MEMORY.md as the index and per-topic files such as user_role.md or feedback_testing.md, toggled via /memory and the autoMemoryEnabled and autoMemoryDirectory settings. Its load contract is that the first 200 lines or 25KB of MEMORY.md enter every session while topic files load on demand. It matters because troubleshooting that conflates missing user feedback with missing CLAUDE.md will misdiagnose the wrong store, and because subagents by default do not inherit main auto memory except for a fork.
$schema, enterprise keys, and the managed-settings.json shape. Lessons add a schema hint at https://json.schemastore.org/claude-code-settings.json for validation, and enterprise keys companyAnnouncements, disableBundledSkills, skillOverrides, availableModels, and claudeMd that gate models and skills at the managed tier and restrict how model and availableModels in user or project files are applied. This is adjacent because the reference page's managed policy overrides both project and user is the correct hierarchy intuition, but the exact mechanism is the managed settings.json ceiling plus the claudeMd string key that can replace a managed CLAUDE.md file.
Permission modes at runtime and AGENTS.md interop. The core capabilities lesson documents runtime permission modes default, plan, acceptEdits, auto, dontAsk, and bypassPermissions with per-mode tables for read, edit, write, and shell behavior, plus the /permissions live toggle. The memory guide adds that Claude Code reads CLAUDE.md, not AGENTS.md, and treats @AGENTS.md or a symlink as the interop path. Both are adjacent because exam scenarios often pair a hierarchy diagnosis with a permission-mode or interop distractor that this task alone does not explain.
| Topic | Why it matters for this task | Primary lesson slug |
|---|---|---|
Path-scoped rules with paths frontmatter and brace expansion | Correct fix for file-type scoping and for bloated always-loaded guidance | claude-code-mdc-config |
Rules without paths versus with paths (always-loaded vs lazy) | Explains which files survive /compact and which reload on matching read | configuration |
Skills as on-demand loading distinct from CLAUDE.md | Correct alternative when guidance is conditionally relevant, not per-session | configuration |
claudeMdExcludes walk filtering and CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD gate | Explains why a valid file on disk is not in /context | configuration and claude-code-mdc-config |
Auto memory at ~/.claude/projects/<project>/memory/ and autoMemoryEnabled | Prevents misdiagnosing authored memory versus model-written memory | configuration |
settings.json precedence and $schema plus enterprise keys | Shows that project policy ceiling is managed settings, not memory | configuration |
Runtime permission modes plan, acceptEdits, auto, dontAsk, bypassPermissions | Paired with hierarchy in scenario questions about destructive accidents | core-capabilities |
AGENTS.md interop via @AGENTS.md or symlink | Explains single-source interop when a repo already uses AGENTS.md | configuration |
Worked production examples
Each example traces a full diagnostic or rollout story with concrete files, observable proof points, and the failure mode that the correct tier avoids.
Worked production examples: Example A: new teammate receives inconsistent naming despite same branch
Starting state. A team of four has a repository with a 400 line project CLAUDE.md at the root that covers naming conventions, error patterns, architecture, and testing. One senior member's Claude sessions respect REST endpoint naming GET /api/v1/resources, camelCase with error.details, and Vitest with supertest, while a new hire who cloned last week sees PascalCase endpoints and jest snippets on the same branch. Code review catches the drift only after merge requests.
Diagnosis chain. Check sharing contract: project CLAUDE.md at ./CLAUDE.md or ./.claude/CLAUDE.md is committed and reaches every clone, while ~/.claude/CLAUDE.md does not travel through git. Run /context in both developers' sessions and compare the Memory files list. The senior member's list shows an extra entry such as ~/.claude/CLAUDE.md that the new hire lacks, or the project file is an empty stub. Run /memory to open each side's actual files and confirm the naming block lives only in the senior member's home directory file. The root cause is therefore scope mismatch, not a model or tool difference.
Fix with import composition to avoid future copy. Move the naming block into a single canonical file such as .claude/standards/api-conventions.md committed alongside the repository, then point the project file at it via a relative import, and verify via /context. Because imports are eager and relative to the containing file, the path must be relative to .claude/CLAUDE.md, not to root, and the file must be committed so future clones resolve it.
The before and after layout is visible in the canonical layer example above, but the diagnostic proof is what matters on the exam: if the convention were truly at project tier, /context would list the project file for both developers; if it appears only for the author, it is at user tier. The correct fix always moves the text to project tier and commits, rather than distributing a paste into every teammate's ~/.claude/CLAUDE.md or asking teammates to run /memory to activate it, because /memory does not trigger loading and user files still would not travel.
Failure avoided. Without the move, every new clone reintroduces the inconsistency, and any attempt to "strengthen" the user file with IMPORTANT or YOU MUST cannot guarantee the result because memory remains probabilistic and may be picked arbitrarily on conflict. The durable fix converts guidance into shared, committed context that every session loads at start, not into a personal file that only its owner loads.
Worked production examples: Example B: monorepo with colliding package conventions
Starting state. A monorepo has packages/api and packages/web under a single root. The root CLAUDE.md contains roughly 800 lines that try to hold both API rules (Express.js, Zod validation, authMiddleware) and frontend rules (React 19, Next.js, Tailwind CSS) with conditional prose such as When working in backend, apply these settings. Developers working in packages/web report that sessions occasionally suggest Express patterns, and sessions working in packages/api sometimes emit React component scaffolds with shadcn/ui phrasing that does not belong.
Diagnosis chain. The conditional prose anti-pattern is flagged explicitly: writing When in backend/, apply these settings: inside the root file does not gate anything, because the file is always loaded for every session regardless of working directory. Check /context from two working directories: from root and from inside packages/api. The root list shows only the walk-up chain, while the packages/api session after reading a file inside that directory also shows packages/api/CLAUDE.md. With a single monolithic root file, both rule families are always present and the model bleeds conventions across packages, quantified in review notes as a noticeable share of edits showing cross-application even after header sharpening.
Fix with directory tier plus selective imports versus path-scoped rules. The correct split depends on which axis the conventions follow.
- If relevance follows the directory tree, keep package-specific guidance in that package's
CLAUDE.mdand keep only genuinely universal text at the root; directory files load lazily only when Claude reads inside that package, so a session working only inpackages/webnever pays forpackages/api/CLAUDE.md. - If relevance follows file type across non-contiguous locations such as
*/migrations/.sqlscattered acrossdb/migrations,services/auth/migrations, andtools/data/migrations, a directory file per folder duplicates guidance and still misses files; the correct mechanism is.claude/rules/withpathsfrontmatter scoped by extension.
A publishable split therefore looks like a thin project root that holds only universal commands and state placement, a committed packages/api/CLAUDE.md with API conventions, a committed packages/web/CLAUDE.md with frontend conventions, and shared linting standards imported selectively so each package pulls only what it needs rather than duplicating a single source of truth.
Failure avoided. The monolithic conditional approach leaves both families in context for every session, which is exactly the shape that produces arbitrary bleed. The decomposed shape keeps each session's context proportional to the package it is touching, and the lazy loading also preserves the /compact guarantee: only the root returns automatically, package files return on next matching read rather than polluting the post-compact context for an unrelated package.
Worked production examples: Example C: noisy discovery and the excludable versus non-excludable boundary
Starting state. A platform team shares an orchestrator script that invokes claude --add-dir ../shared-lib to make a shared library reachable, and a polyglot monorepo that carries vendored SDK copies under vendor/ and generated GraphQL clients under __generated__. Developers report that Claude sessions in unrelated packages occasionally quote conventions from a vendored CLAUDE.md and from another team's CLAUDE.md that appeared via an ancestor directory, while the shared library's conventions never appear despite --add-dir.
Diagnosis chain. Discovery is governed by two separate filters. Walk-level exclusion via claudeMdExcludes suppresses paths from the walk before concatenation, matched against absolute file paths, merging across tiers, and unable to exclude managed policy. Per-file gating via paths inside .claude/rules/ is the post-discovery gate that loads authored type conventions only when touched files match. --add-dir itself only extends the walk to additional roots; by default those roots' CLAUDE.md chains are not loaded until the environment gate CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 is set.
Fix with explicit opt-in rather than silent expectation. Three concrete changes close the gap. First, make the walk exclusion explicit in a local or project settings.json so vendored and generated trees never enter discovery. Second, set the --add-dir gate variable when the shared library's conventions should be part of the session. Third, where a convention should follow a type wherever it appears, such as */.ts except generated, use a path-scoped rule with negation rather than a walk exclusion, because that decision is about authored type conventions, not about noisy discovery. The distinction between the two exclusion stages is explicitly called out in the walk example in the mechanism section.
Failure avoided. Relying on .gitignore or on an imagined .claudeignore does not suppress discovery; the real control is claudeMdExcludes. Expecting --add-dir alone to load memory leaves the session with reachable code but missing instructions, and the fix is a one-variable opt-in, not a change of working directory or a manual paste into the primary CLAUDE.md.
Worked production examples: Example D: determinism boundary where memory alone fails
Starting state. A project adds Always run clang-format after editing or Never modify config/ to CLAUDE.md or to a path-scoped rule, yet pull requests occasionally arrive with unformatted diffs or with writes inside config/. Attempts to reinforce the prose with IMPORTANT or repeated emphasis do not close the gap, and the oversized guidance file grows past 500 lines, further reducing adherence.
Diagnosis chain. CLAUDE.md is guidance shaped probabilistically as a user message, not deterministic enforcement, and oversized files lose adherence even when IMPORTANT is present. The guide's explicit fix is to use a hook that fires at a fixed lifecycle event regardless of what the model decides. Settings-level permissions.deny is the companion for blocking. Choosing between the two is a lifecycle question: PreToolUse to block before execution versus PostToolUse to auto-fix after a successful edit.
Fix as two required patterns. This task requires both settings-based and hook-based enforcement examples to be shown as substantial, language-tagged blocks. The next section provides the complete settings.json and hooks configurations that the team should have committed instead of relying on memory. The observable outcome is that a blocked write never executes and a formatting run normalizes the diff before the model reports completion, which prose instructions alone cannot promise.
Settings-based enforcement, hook-based enforcement, and Agent SDK embedding
These three examples are the enforcement half of the hierarchy. Each is a complete, language-tagged configuration with an explanation of what it proves, where it fails if miswired, and what the observable output looks like.
Settings-based enforcement, hook-based enforcement, and Agent SDK embedding: Example 3: settings-based enforcement with permissions rules in settings.json
This example encodes the "must never" and "must ask" guarantees that CLAUDE.md cannot provide, using the permissions object that the client evaluates deny-first before the model acts. It commits at project tier as .claude/settings.json so the policy travels via git, with a local override layer at .claude/settings.local.json for personal exceptions that should not ship to teammates.
// File: .claude/settings.json (project tier, committed, shared via git)
// Purpose: deterministic guarantees for tool, network, and scope boundaries
// Enforcement: client evaluates `deny` before anything else; lists merge across tiers
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Read(**/*)",
"Grep(**/*)",
"Glob(**/*)",
"Bash(npm test)",
"Bash(npm run lint)",
"Bash(npm run typecheck)",
"Bash(ls *)",
"Bash(git status)",
"Bash(git diff *)"
],
"ask": [
"Bash(npm install *)",
"Bash(rails db:migrate*)",
"Bash(rake db:migrate*)",
"Edit(**/*)",
"Write(**/*)"
],
"deny": [
"Bash(scp *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(nc *)",
"Bash(rm -rf *)",
"Bash(sudo *)",
"Bash(chmod 777 *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(./config/prod.json)",
"Write(./config/**)",
"Edit(./config/**)"
]
},
"claudeMdExcludes": [
"**/vendor/**",
"**/__generated__/**",
"**/monorepo/other-team/.claude/rules/**"
],
"model": "team-default",
"hooks": {}
}// File: .claude/settings.local.json (local tier, gitignored, personal)
// Purpose: personal overrides for this project on this machine without affecting teammates
// Precedence: local above project; a local `allow` does not outrank a managed or project `deny`
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm test -- --watchAll=false)"
],
"deny": []
},
"claudeMdExcludes": [
"/home/user/monorepo/other-team/.claude/rules/**"
]
}What this proves. Read and search tools are safe and cheap, so they are allowed without prompt; writes and package installs stay behind ask so a human confirms; destructive and network tools are denied permanently even if a permissive acceptEdits mode or a local allow would otherwise auto-approve them. Sensitive files such as .env and ./secrets/** are unreadable regardless of what CLAUDE.md suggests, which is the correct answer whenever an option proposes to "add a CLAUDE.md instruction to never read .env". The claudeMdExcludes array keeps vendored and generated instructions out of discovery so they never reach concatenation, with managed files explicitly non-excludable.
Failure boundaries. A bare Bash(scp *) style pattern blocks the subcommand class; the parenthesized pattern is the documented form for built-in tools, not a wildcard tool name except after mcp__<server>__. An allow entry added locally via "Yes, and do not ask again" writes to .claude/settings.local.json and therefore does not outrank a project or managed ask or deny for the same tool, which is visible when the prompt reappears despite the prior approval; the fix is to put the rule at the correct tier or to use the destination picker to write to the project's shared file where appropriate. Lists merge, so adding a project deny does not erase a user allow; the deny still wins for that pattern because of deny-first evaluation, not because the array replaced.
Observable output. Launch claude from the repository root and run /status; the Setting sources line shows User settings, Project settings, and Project local settings as loaded files, and for a managed deployment also shows the managed source. Attempt Bash(scp file host:) or Read(./.env) in a session; the client blocks before execution and surfaces a decision rather than asking the model, which is the direct contrast with CLAUDE.md phrasing that shapes behavior but is not a hard layer.
Settings-based enforcement, hook-based enforcement, and Agent SDK embedding: Example 4: hook-based enforcement for formatting and blocking
Hooks are the lifecycle guarantee that survives per-turn model variation. They run at fixed points such as PreToolUse, PostToolUse, PostToolUseFailure, SessionStart, InstructionsLoaded, FileChanged, CwdChanged, PreCompact, and Stop, and each handler type - command, http, mcp_tool, prompt, or agent - is declared under hooks.<EventName> with a matcher that filters by tool name or literal filename.
// File: .claude/settings.json (project tier, committed, enforcement via hooks)
// Two hooks replace two CLAUDE.md phrases that cannot guarantee per-run behavior
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": ["Read(**/*)", "Grep(**/*)", "Glob(**/*)"],
"deny": ["Bash(scp *)", "Bash(curl *)", "Bash(rm -rf *)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/deny-config-write.sh"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-network-and-rm.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-after-edit.sh"
}
]
}
],
"InstructionsLoaded": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/log-instructions.sh"
}
]
}
]
}
}#!/bin/bash
# File: .claude/hooks/deny-config-write.sh (PreToolUse, blocks Write or Edit under config/)
# Reads hook JSON on stdin, denies writes to ./config/** deterministically
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
if [[ "$FILE_PATH" == *"config/"* ]]; then
jq -n --arg reason "Writes to config/ are not allowed; use the approved config pipeline." '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: $reason
}
}'
exit 0
fi
exit 0#!/bin/bash
# File: .claude/hooks/block-network-and-rm.sh (PreToolUse on Bash)
# Separate handler so matcher plus `if` grouping is explicit; see hook resolution diagram
set -euo pipefail
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -Eq 'scp |curl |wget |nc |rm -rf'; then
jq -n --arg reason "Destructive or network command blocked by hook" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: $reason
}
}'
exit 0
fi
exit 0#!/bin/bash
# File: .claude/hooks/format-after-edit.sh (PostToolUse, reactive fix after Write or Edit)
# Runs formatter deterministically; rewriting tool output is not needed here
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
if [[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]]; then
exit 0
fi
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx|*.json|*.md)
npx prettier --use-tabs=false --tab-width=4 --write "$FILE_PATH" >/dev/null 2>&1 || true
;;
esac
jq -n --arg msg "PostToolUse: formatted $FILE_PATH if applicable" '{
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: $msg
}
}'#!/bin/bash
# File: .claude/hooks/log-instructions.sh (InstructionsLoaded)
# Logs which CLAUDE.md and .claude/rules/*.md were loaded and why
set -euo pipefail
INPUT=$(cat)
echo "$INPUT" | jq -r '[.hook_input // .] | @json' >> "${CLAUDE_PROJECT_DIR}/.claude/logs/instructions.log" 2>/dev/null || true
exit 0What this proves. PreToolUse with a Write|Edit matcher that inspects tool_input.file_path deterministically blocks modifications under config/ even when CLAUDE.md says the same thing, because the hook fires before execution regardless of model choice. PostToolUse with the same matcher guarantees that prettier runs after every successful edit, replacing always run formatter after editing prose that is occasionally skipped with normalization that cannot be skipped. InstructionsLoaded gives a persistent record of which instruction files were loaded and when, which is the audit alternative to repeatedly running /context during a long session.
Failure boundaries. A missing or too-broad matcher is the most common miswiring: a Bash-matcher hook that is supposed to gate writes never fires for Write calls, and a FileChanged matcher that uses a glob or regex never matches because that event matches literal filenames only. Hooks run in parallel per event and merge by deny above defer above ask above allow, so a single deny from one handler wins over an allow from another. Exit codes matter: 0 means no hard block (with optional JSON body for structured deny, ask, defer, or allow and optional updatedInput or updatedToolOutput and additionalContext), 2 is a hard block via stderr feedback, and any other code is treated as a hook error that still allows the operation but logs a warning. Inspect live wiring with /hooks to confirm matcher, type, and recent firings before adding log statements.
Observable output. After committing the hook directory and settings.json, run claude --debug-file /tmp/hooks.debug and trigger a Write under config/; the debug log shows hook execution details including exit code and stdout and stderr, and an attempt to write inside config/ is denied with the permissionDecisionReason visible in the transcript. A successful Write outside config/ shows the PostToolUse formatter running and the file normalized to four-space, no-tabs output regardless of the write's initial indentation, which is the deterministic counterpart to the probabilistic CLAUDE.md rule.
Settings-based enforcement, hook-based enforcement, and Agent SDK embedding: Example 5: Agent SDK embedding with explicit settingSources selection
When Claude Code is embedded via the SDK, the caller chooses which filesystem tiers the agent sees. The default of no sources is the reason an SDK agent that ignores project CLAUDE.md and skills is not broken, it simply was never given those layers.
// File: src/agent/run-team-agent.ts (TypeScript, Agent SDK)
// Purpose: spawn an SDK session that sees project team standards but not personal defaults,
// keeps the Claude Code system prompt, and caps cost for the whole session.
import { query } from "@anthropic-ai/claude-agent-sdk";
type AgentMessage = unknown;
export async function runTeamAgent(prompt: string): Promise<void> {
const messages: AgentMessage[] = [];
for await (const msg of query({
prompt,
options: {
// No filesystem settings are loaded by default; select explicitly.
// "project" makes .claude/CLAUDE.md, .claude/rules/, and committed hooks visible.
// "user" would add ~/.claude/CLAUDE.md and ~/.claude/rules/; omitted here to isolate team policy.
// "local" would add .claude/settings.local.json; also omitted for reproducibility.
settingSources: ["project"],
// Keeps the standard Claude Code system prompt rather than a bare model prompt.
systemPrompt: { preset: "claude_code" },
// Evaluation order reminder: hooks run before deny rules, permission mode, and canUseTool
// so a PreToolUse hook that returns permissionDecision "ask" still surfaces for approval
// even when an allow rule would otherwise auto-approve.
permissionMode: "default",
// Dollar cap for the whole agentic session; hitting it reports error_max_budget_usd
// rather than silently truncating.
maxBudgetUsd: 25,
// Keep model selection and provider routing in docs or settings, not hardcoded here.
// Selecting a model string that is not available to the caller's API key fails the session
// rather than silently falling back, which is the correct strict behavior for reproducibility.
}
})) {
messages.push(msg);
// handle message: assistant text, tool call, hook output, or cost footer
}
// Post-loop: verify what the agent actually saw via transcript
// In automation, start with --bare or inspect the transcript rather than relying on
// conversational memory, because the SDK session's context is scoped to this invocation.
}
// Companion variant: isolated personal workflow that must not see team policy
export async function runPersonalAgent(prompt: string): Promise<void> {
for await (const msg of query({
prompt,
options: {
settingSources: ["user"],
systemPrompt: { preset: "claude_code" },
permissionMode: "default",
maxBudgetUsd: 10
}
})) {
void msg;
}
}
// Companion variant: fully reproducible automation that sees both but with team above user
export async function runFullAgent(prompt: string): Promise<void> {
for await (const msg of query({
prompt,
options: {
settingSources: ["user", "project"],
systemPrompt: { preset: "claude_code" },
permissionMode: "acceptEdits",
maxBudgetUsd: 50
}
})) {
void msg;
}
}What this proves. Selecting settingSources: ["project"] is how a team-scoped automation isolates project conventions, rules, and committed hooks from personal verbosity and shortcuts in ~/.claude/CLAUDE.md; the SDK's isolation is the code-level equivalent of claudeMdExcludes at the filesystem tier for human sessions. Adding "user" or "local" back widens the scope predictably, and omitting them keeps the session clean, which is the documented fix for "SDK agent ignores my CLAUDE.md". Using systemPrompt: { preset: "claude_code" } preserves the agent prompt that expects the hierarchy and permission shape described in the earlier sections, while cost routing via maxBudgetUsd caps spend and returns a typed error_max_budget_usd subtype on exhaustion. Provider routing comments reflect the CLAUDE_CODE_USE_BEDROCK and CLAUDE_CODE_USE_VERTEX environment variables without hardcoding them in the source, which keeps the snippet accurate regardless of cloud configuration.
Failure boundaries. Treating settingSources as additive rather than selective is the common mistake: passing ["project"] does not include user by default, so a personal rule that fixes formatting for a human will not appear for the embedded agent unless "user" is added. Conversely, an agent that loads project while the project settings.json still holds path-sensitive or interactive-only keys may behave differently than a human session because a committed permissions.allow that waits for workspace trust does not apply until the folder is trusted; the SDK session in a fresh clone may therefore prompt where a human's long-trusted session does not.
Observable output. Running the first variant against a prompt such as Summarize the repo conventions produces output grounded in the committed .claude/CLAUDE.md plus any .claude/rules/*.md that the gate permits, while the second variant's output is grounded in ~/.claude/CLAUDE.md and personal rules; diffing the two transcripts makes the scope choice visible, which is the SDK analog to diffing /context between two developers in human mode.
Build exercise material
Reproducible steps for the six sub-tasks on the reference page, with the observable proof after each. Perform these from a clean checkout so that missing commits are caught rather than papered over by prior state.
Build exercise material: 1. Create project-level .claude/CLAUDE.md with universal standards
Write CLAUDE.md at the repository root with at least three sections: naming conventions, error handling patterns, and a code review checklist, plus build, test, lint, and typecheck commands.
# File: ./CLAUDE.md (project tier, committed)
Commands
- Build:
npm run build - Test:
npm test - Lint:
npm run lint - Typecheck:
npm run typecheck
Conventions
- Naming:
camelCasefor variables,PascalCasefor types, file per component insrc/components/ - Error handling: wrap with
AppErrorcarryingcodeanddetails; never throw rawErrorfrom handlers - Architecture: Zustand stores in
src/stores/, never inlineuseStatefor shared state
Code Review Checklist
- Zod validation on every endpoint input
- No
anywithout comment explaining why - Tests in
__tests__/adjacent to source,describeanditblocks only
Proof. Run /context from the project root; under Memory files the list shows ./CLAUDE.md as loaded. Opening /memory and selecting the project file reveals the three sections plus commands. Commit the file and clone to a second directory; the clone's /context also shows the project file, confirming team distribution via git.
Code Review Checklist: 2. Create directory-level CLAUDE.md in packages/api
Create CLAUDE.md inside packages/api with REST endpoint naming and request and response schema requirements. Keep the root thin so only universal text lives at root.
# File: ./packages/api/CLAUDE.md (directory tier, committed, lazy) # Loading note: only when Claude reads a file inside packages/api/
API package
- REST endpoints:
GET /api/v1/resources,POST /api/v1/resources,error.detailsarray on failures - Validation:
authMiddlewareplus Zod on every endpoint; schemas usecamelCase - Routes in
packages/api/src/routes/, services inpackages/api/src/services/
Proof. From the project root, run /context before touching any file in packages/api; the directory file does not yet appear. Read a file under packages/api, then run /context again; the list now shows both the project file and packages/api/CLAUDE.md as active. The reverse holds in packages/web: that sibling directory file is absent until a file there is read.
API package: 3. Create .claude/rules/testing.md with test-specific conventions
Place a rules file with at least three conventions in .claude/rules/testing.md. Without paths it loads unconditionally alongside .claude/CLAUDE.md; with paths it would load only for matching files.
# File: ./.claude/rules/testing.md
Testing conventions
- Test naming pattern:
*.test.tscolocated in__tests__/adjacent to source - Assertion style: use Vitest
expectplustoMatchInlineSnapshotfor golden outputs - Fixture usage: shared factory at
tests/fixtures/factory.ts; do not duplicate factories per package
Proof. Run /context at project root; the rules file appears under Memory files alongside .claude/CLAUDE.md. If the file used --- frontmatter with paths: ["*/.test.ts"], it would appear only after reading a test file, which is the conditional variant used for the token-efficiency story.
Testing conventions: 4. Use @path import in project CLAUDE.md to reference shared standards
Add a bare @ line relative to .claude/ that points at a committed standards file, and ensure the target exists at the relative path.
# Append inside ./.claude/CLAUDE.md (project tier)
# Shared standards imported eagerly at load
@./standards/naming.md
# Mention without importing: wrap in backticks
Reference literal @./standards/not-imported.md in prose; it stays literal.
# File: ./.claude/standards/naming.md (committed, shared single source of truth)
Naming conventions
PascalCasefor exported types and components,camelCasefor variables and functions- File names: kebab-case for routes, camelCase for utilities
- State stores:
createFooStorepattern, one file per store insrc/stores/
Proof. Run /context; the imported content appears inline as if its text lived inside .claude/CLAUDE.md and does not create a separate top-level entry. An import written inside backticks or inside a fenced code block does not produce an entry, confirming span safety. A missing target at the import path silently produces a gap rather than an error, which is visible as missing text rather than a diagnostic.
Naming conventions: 5. Run /context in different directories to verify the loaded set
From three working directories - repository root, packages/api, and packages/web - run /context and record the Memory files list.
Expect in root: managed policy if deployed, user ~/.claude/CLAUDE.md where present, project CLAUDE.md, .claude/CLAUDE.md, rules without paths, and the inlined import content as part of the project tier. Expect in packages/api after reading a file inside it: the same plus packages/api/CLAUDE.md and any packages/api/CLAUDE.local.md. Expect in packages/web after reading there: root plus that sibling's directory file instead, never both package files at once. This demonstrates that the diagnostic commands reveal the loaded set while the loading itself happens automatically by location, not by running the command.
Naming conventions: 6. Move one convention to user level ~/.claude/CLAUDE.md and confirm the scoping boundary
Cut one convention, for instance Indentation: 4 spaces, no tabs, run prettier on save, from the project file into ~/.claude/CLAUDE.md on your machine, then compare two sessions.
# File: ~/.claude/CLAUDE.md (user tier, personal, not committed)
Personal override for this machine
- Indentation: 4 spaces, no tabs; local formatter alias
fmtisnpx prettier --tab-width 4 --write
# Observer step from a clean second clone or teammate machine # Their /context shows project CLAUDE.md but not the user entry above # Your /context shows both
Proof. Your own /context continues to list the user file plus the project file, while a fresh clone or a second user session that does not share your home directory shows only the project file and never the personal entry. This is the exact boundary that the new-teammate scenario tests: team standards that live in ~/.claude/CLAUDE.md never reach a clone, so the fix is to move them back to project tier and commit.
A companion verification for deterministic versus advisory is to place never run scp in ~/.claude/CLAUDE.md for the session that blocks, then check that a tool request is still probabilistically shaped rather than hard denied; the durable block requires permissions.deny with Bash(scp *) in settings.json at project or managed tier, not a memory line.
Mechanism and API surface
- Walk up chain and lazy discovery
- Eager walk up at launch from working directory to root, plus lazy load of subdirectory CLAUDE.md and path scoped rules only when a file in that subtree or matching that glob is read. Per package cost is paid only when that package is touched.
- @path import semantics
- Directive is @path/to/file inline with no keyword, relative to importing file, recursive up to four hops, skipped inside code spans and fenced blocks, imported content loads inline at launch.
- claudeMdExcludes glob
- Array in .claude/settings.json of globs against CLAUDE.md file paths to skip, not against directories applied to. Use for noisy monorepos where another team's rules keep surfacing.
- --add-dir gate
- Flag makes extra directory reachable, CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD equals 1 gates loading of that directory's CLAUDE.md. Code reachable does not imply instructions loaded.
- SDK settingSources and systemPrompt preset
- Agent SDK query settingSources selects which filesystem layers are honored, none by default, must include project to see project CLAUDE.md and skills. systemPrompt preset claude_code preserves standard prompt when embedding.
- Diagnostic pair /memory and /context
- /memory lists known locations as the guide keyed answer, /context reports which files actually loaded under Memory files in the running session, neither triggers loading, both diagnose what is already present.
The decision rules in play
Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.
User scope lives at `~/.claude/CLAUDE.md` and never travels through git
Claude Code discovers a user-level memory file at ~/.claude/CLAUDE.md on the local machine before any repository file is considered. The file lives outside every repository in the home directory and is not tracked by git. Every Claude Code session started by that user loads this file as baseline personal context alongside any project and directory files that are also discovered for the current working directory. The file is personal by construction and the discovery mechanism has no network or sharing step that would copy it to another machine.
The product separates personal preferences from team conventions at the filesystem tier. User scope is intentionally not version controlled so that personal verbosity preferences, shortcut aliases, output style choices, and editor habits do not require committee review and do not pollute team history. The cost of that privacy is that no other developer can observe the file without an explicit out-of-band copy operation. The examined items hammer this separation repeatedly because it is the primary root cause for inconsistency between teammates.
Boundary. The rule flips when a preference must be universal. If the content describes a team-wide API naming convention, error handling pattern, fixture location, or review checklist that every future clone must enforce, the same text belongs at project scope in CLAUDE.md or .claude/CLAUDE.md and must be committed. User scope is correct only when the preference is strictly per person and should not appear in another person's session. A common mutation that makes user scope wrong is when a standard was authored by one engineer and works for that person but no one else sees it after cloning.
Recurring specifics. - Canonical path is ~/.claude/CLAUDE.md in every item that tests personal preferences. - Distinguishing property phrased as not shared via git or not version controlled or personal and not shared. - Example personal content repeatedly cited: single versus double quotes, 2 versus 4 space indentation, trailing commas, verbose logging, single quotes for strings, always use tabs, ruff versus flake8, TypeScript strict mode as solo preference. - Commit and clone language paired with user scope: clone the repository, pull the latest commits, new teammate still does not see the rule. - Opposite tier concrete paths for contrast: root CLAUDE.md, .claude/CLAUDE.md inside .claude directory.
Proposal. place team standards in ~/.claude/CLAUDE.md and assume teammates will inherit them on clone.
Why it attracts. it works for the author so it feels like it works globally.
Why it fails. user scope does not travel, so the clone receives nothing.
When it would be right. when the instruction is genuinely personal and intentionally not shared.
Proposal. add override: true or similar directive to force user scope to win.
Why it attracts. it promises a precedence knob for conflicts.
Why it fails. no such field exists in CLAUDE.md semantics.
When it would be right. never, the fix for guaranteed wins is settings.json or hooks, not a frontmatter flag.
Proposal. run /memory to activate user scope for teammates.
Why it attracts. /memory is associated with memory files.
Why it fails. /memory is diagnostic, loading happens automatically from tier not from command invocation.
- - Mutation replaces formatting rule with API naming or database abstraction convention. Answer stays user versus project scope, only domain vocabulary changes. - Mutation adds enterprise managed policy alongside user and project. Correct answer shifts to managed tier winning, but user scope still loses to project for team distribution.
Project scope lives at `CLAUDE.md` or `.claude/CLAUDE.md` in the repository and is shared via version control
Claude Code discovers project-level memory at two valid locations inside the repository: CLAUDE.md at the repository root and .claude/CLAUDE.md inside the .claude directory. Both are treated as project tier, both are version controlled, and both are automatically cloned and pulled with the repository. Any instruction placed there is loaded for every developer who works in that repository when the session starts from a directory inside the repo. Items that show three active files explicitly count both root and .claude/CLAUDE.md as active simultaneously.
The design goal for project scope is reproducibility on fresh checkout. Placing conventions inside the repository makes git clone the distribution mechanism. No onboarding script, wiki copy, or manual paste is required. The examined items use this as the correct fix for every new-teammate-inconsistency scenario because commit is the only operation that reaches future teammates.
Boundary. Project scope becomes wrong when the content is strictly personal or when it is conditional on file type or directory. Personal verbosity or personal shortcut belongs at user scope. Conventions that should trigger only when editing .py under jobs/ or /.test.ts or Terraform under terraform//* belong in path-scoped .claude/rules/ with paths frontmatter or in a directory-specific CLAUDE.md. The boundary is universality: if the rule should apply to every edit in the repository, project scope is correct. If it should apply only to a subset of files, a more targeted tier is correct.
Recurring specifics. - Valid paths repeated as CLAUDE.md at root and .claude/CLAUDE.md and sometimes repository root CLAUDE.md. - Shared property phrased as committed to the repository or version controlled or checked in. - Canonical team content: API naming conventions, error handling patterns, testing requirements, architecture decisions, code review checklist, fixture directory path, lint command, commit message format like [TICKET-ID] Brief description. - Mechanism for sharing slash commands paired alongside: .claude/commands/ at project root committed so every clone receives command.
Proposal. link to a private Gist or wiki from README instead of committing.
Why it attracts. avoids editing memory file.
Why it fails. linking does not cause automatic loading into Claude Code sessions.
When it would be right. for supplemental reading not for always-loaded conventions.
Proposal. add CLAUDE.md rules via .claude/config.json with a rules array.
Why it attracts. looks like structured config.
Why it fails. fabricated surface, not a real discovery mechanism.
Proposal. use CLAUDE.local.md as the sharing tier.
Why it attracts. name suggests local project memory.
Why it fails. by convention it is gitignored and not shared.
- - Mutation switches between
CLAUDE.mdat root versus.claude/CLAUDE.mdas the named project path. Both count as correct project tier, items explicitly call both valid. - Mutation adds count of active files, for example root plus.claude/CLAUDE.mdpluspackages/api/CLAUDE.mdto test that multiple project-tier files coexist additively.
Directory scope lives at `subdirectory/CLAUDE.md` and supplements project scope for that subtree
Claude Code supports CLAUDE.md files inside subdirectories such as backend/CLAUDE.md, frontend/CLAUDE.md, packages/api/CLAUDE.md, services/worker/CLAUDE.md, or src/mobile/CLAUDE.md. When the active work context is inside that directory, Claude Code treats both the project root files and the relevant subdirectory file as active. The subdirectory file does not replace the root, it adds or specializes guidance for that package. Items consistently phrase the active set as root CLAUDE.md plus the CLAUDE.md for the current working directory.
Large repositories have divergent stacks, compliance needs, and domain rules that differ by package. A root file that tries to hold every package convention in prose conditionals like When in backend/, apply these settings: becomes unwieldy and the model follows it inconsistently. Directory memory gives a filesystem-gated scope that matches ownership boundaries between teams and lets each package maintain its own conventions without editing the root.
Boundary. Directory scope is correct when relevance follows the directory tree. It becomes the wrong choice when relevance follows file type across non-contiguous locations. If conventions must follow /migrations/.sql scattered across db/migrations/, services/auth/migrations/, and tools/data/migrations/, or follow /.test.tsx colocated beside components in dozens of feature folders, a directory CLAUDE.md per folder duplicates guidance and still misses files. For those axes, .claude/rules/ with paths glob frontmatter scoped by extension is the correct mechanism.
Recurring specifics. - Example directories cited: /packages/api/, /packages/frontend/, backend/, frontend/, payments/, src/api/, src/components/, ios/, android/, terraform/. - Example content gated by directory: REST conventions, request/response schemas, React functional components with hooks, SQLAlchemy ORM not raw SQL, ruff check versus npm run lint per service. - Incorrect conditional phrasing called out: When in backend/, apply these settings: inside root file flagged as unsupported conditional block.
Proposal. put all conventions in one root CLAUDE.md under headers and trust the model to infer directory from content.
Why it attracts. simpler repository layout.
Why it fails. model bleeds conventions across packages, items quantify roughly 18% of edits showing cross-application even after header sharpening.
Proposal. place .claude/settings.json effects inside subdirectories such as backend/.claude/settings.json.
Why it attracts. mirrors directory tier intuition.
Why it fails. not a supported pattern, settings scopes are user, project, local, managed, not per-subdirectory directories.
Proposal. treat directory file as full replacement of root.
Why it attracts. suggests clean override semantics.
Why it fails. files are additive, root rules remain active and directory rules supplement them for that context.
- - Mutation varies which subdirectory is named so student must distinguish sibling loading: working in
/packages/frontend/does not activate/packages/api/CLAUDE.md. - Mutation sets root and subdirectory in direct conflict such asuse UTCversususe local timezonefor payments. Both remain present with subdirectory specialization for that subtree.
Local scope via `CLAUDE.local.md` appends after its sibling `CLAUDE.md` within the same directory level
At any level in the hierarchy, a file named CLAUDE.local.md may sit next to CLAUDE.md. Claude Code discovers it alongside the sibling and appends its content after the sibling at that tier. The conventional expectation is that CLAUDE.local.md is gitignored so personal or temporary notes stay personal. Conceptually it is a project-scoped version of user memory with narrower radius: personal quirks for this repository without committing them.
Teams need a place for scratch notes that are tied to a particular repository checkout but should not flow to teammates. A developer's favorite scratchpad path, a verbose explanation kept for repeated pasting, or a temporary debugging note fits here. Giving it a parallel file name with .local suffix keeps shared CLAUDE.md clean while still granting that content automatic loading for the owner.
Boundary. The suffix does not confer precedence or guarantee. Reading last does not mean winning a contradiction, the guidance still concatenates and conflicts may resolve arbitrarily. If the local note expresses a team rule, that rule belongs in CLAUDE.md not in CLAUDE.local.md. If the requirement is deterministic enforcement such as blocked tools or required formatter, it belongs in settings.json or hooks, not in any memory file.
Recurring specifics. - File name CLAUDE.local.md appears alongside CLAUDE.md in reference and memory docs discussion. - Property gitignored by convention or not committed or personal notes are the last thing read at that level. - Load order phrasing: CLAUDE.local.md is appended after CLAUDE.md at the same level.
Proposal. keep team indentation rule in CLAUDE.local.md at project root so it appends last and overrides personal preference.
Why it attracts. later feels like higher priority.
Why it fails. concatenation does not confer deterministic win, correct hard guarantee is a PostToolUse hook or settings enforcement.
Proposal. promote personal preference into every teammate's CLAUDE.local.md via paste.
Why it attracts. keeps project file untouched.
Why it fails. still per-person copy with drift, team standard must be committed project file.
- - Mutation may frame local file at user tier versus project tier; both are valid positions for a
.localvariant but exam focuses on project root adjacent usage. - Mutation comparesCLAUDE.local.mdload order guarantee confusion with settings precedence chain.
All discovered `CLAUDE.md` files are concatenated into context with no hard override guarantee
Claude Code collects every applicable memory file along the walk from filesystem root down to the current working directory and produces a single concatenated context block that is delivered to the model as a user message. No file replaces another and no tier silently drops a conflicting instruction. Three project files can be active at once, for example repository root CLAUDE.md plus .claude/CLAUDE.md plus packages/api/CLAUDE.md, and all three contribute text. The reference docs phrase this as All discovered files are concatenated into context rather than overriding each other.
# CLAUDE.md at repository root
- Use `4` space indentation matching existing codebase
- Commit format `[TICKET-ID] Brief description`
# packages/api/CLAUDE.md active alongside root when working in packages/api
- REST endpoint naming `GET /api/v1/resources`
- Request and response schemas use `camelCase` with `error.details` arrayMemory is behavioral guidance not configuration with deterministic dispatch. The model weighs memory probabilistically alongside other context. Making concatenation explicit avoids implying a specificity chain that the product does not enforce. The examined items return to this phrasing whenever a distractor claims more specific scope wins or inner files completely override the root.
Boundary. The opposite case is settings.json and hooks. Those surfaces are enforced by the Claude Code client itself, deny rules are evaluated before the model acts, and hooks fire on lifecycle events regardless of what the model decides. If a rule must hold on every run such as never execute Bash(scp *) or always run formatter after edit, concatenation is the wrong mechanism.
Recurring specifics. - Quote fragment: concatenated into context rather than overriding each other. - Companion quote: if two rules contradict each other, Claude may pick one arbitrarily. - Companion quote: there is no guarantee of strict compliance. - Example active set: All three - project root + .claude/CLAUDE.md + /packages/api/CLAUDE.md are active together.
Proposal. directory CLAUDE.md completely replaces root inside that directory.
Why it attracts. mirrors override intuition.
Why it fails. files supplement not replace, both load.
Proposal. most recently modified file wins.
Why it attracts. suggests recency tie-break.
Why it fails. no recency evaluation in memory loading.
Proposal. only closest file to workspace root is used.
Why it attracts. simplifies hierarchy.
Why it fails. ignores additive design. Example of concatenation expectation: markdown # CLAUDE.md at repository root - Use 4 space indentation matching existing codebase - Commit format [TICKET-ID] Brief description # packages/api/CLAUDE.md active alongside root when working in packages/api - REST endpoint naming GET /api/v1/resources - Request and response schemas use camelCase with error.details array Both blocks appear in context together. No file is dropped.
- - Mutation varies number of claimed active files to test additive counting: one versus two versus three. - Mutation swaps guidance quote with settings quote to test whether student knows which surface has strict precedence.
Load order is filesystem root down to working directory with `CLAUDE.local.md` last at each level
The documented load order is broadest scope to most specific. The chain starts at the filesystem root and walks down to the directory where Claude Code was launched, collecting memory along that path. Instructions closer to the launch directory are read later in the concatenated block. Within any single directory, CLAUDE.local.md is appended after CLAUDE.md at that same level, so personal notes are the final text at that tier. This is load order, not a precedence chain.
# Load sequence for cwd packages/api under /context Memory files
~/.claude/CLAUDE.md -> repo/CLAUDE.md -> repo/.claude/CLAUDE.md -> repo/packages/api/CLAUDE.md -> repo/packages/api/CLAUDE.local.mdOrdering reflects narrative shape: universal standards first, then project standards, then package specialization. Reading specific guidance last places it near the end of context which helps locality, but the docs explicitly separate order from guarantee. Reading last does not confer deterministic win on conflict.
Boundary. If correctness required guaranteed win of inner over outer, order alone would be insufficient and the requirement belongs in settings.json or a hook. Order helps the model but does not enforce. For gating such as never write to config/ or never run scp, enforcement surface is required regardless of order.
Recurring specifics. - Order phrase: from the filesystem root down to your working directory. - Companion phrase: instructions closer to where you launched Claude are read last. - Within-directory order: CLAUDE.local.md is appended after CLAUDE.md. Example of correctly ordered walk for a developer working in packages/api/:
- - Mutation moves developer from
packages/api/topackages/frontend/to test sibling exclusion alongside order. - Mutation adds enterprise managed memory before user and project to probe managed-first ordering.
Contradictory instructions may be resolved arbitrarily by the model with no guaranteed winner
When two memory files contain directly opposing instructions such as always use single quotes for strings at ~/.claude/CLAUDE.md versus always use double quotes for strings at project CLAUDE.md, both strings remain in context. The docs state if two rules contradict each other, Claude may pick one arbitrarily and there is no guarantee of strict compliance. No tier suppresses the other before the model reasons.
Memory is delivered as a user message which is probabilistic guidance. The model weighs that guidance against other context including existing code style and explicit user requests. Unlike a deny rule which the client enforces before the tool executes, memory has no gate that can drop one branch of a contradiction after concatenation.
Boundary. Guarantee becomes available in enforcement surfaces. A PreToolUse hook that blocks Write to config/ or a PostToolUse hook that runs prettier --use-tabs=false --tab-width=4 --write after every edit creates deterministic post-generation normalization. Similarly, permissions rules in .claude/settings.json with permissions.deny such as Bash(scp *) are evaluated deny-first and block even if another scope allows.
Recurring specifics. - Example contradictory pair: single quotes versus double quotes, 2 versus 4 space indentation, snake_case versus camelCase for Python variables. - Distractor claim flagged: more specific scope wins on conflicts described as popular paraphrase the official docs never make.
Proposal. personal file always wins because it loads after project.
Why it attracts. leverages load-order intuition.
Why it fails. later does not mean winning when both are concatenated user messages.
Proposal. project file always wins because it is more specific.
Why it attracts. matches specificity intuition.
Why it fails. not what memory docs promise.
Proposal. model detects conflict and asks which rule to apply.
Why it attracts. sounds safe.
Why it fails. no built-in conflict prompt for memory.
- - Mutation changes contradictory domain from style to secrets or logging to test whether principle generalizes beyond formatting. - Mutation pairs contradiction with enterprise managed policy to test that managed settings win via enforcement tier not via memory concatenation.
`CLAUDE.md` is guidance delivered as user message not deterministic enforcement
Instructions in CLAUDE.md at any tier are loaded as context at session start and supplied to the model as a user message. The docs note that memory is delivered as a user message not as part of the system prompt and that it shapes behavior probabilistically. The same content placed in settings.json or a hook would be enforced by the client regardless of model choice.
# .claude/CLAUDE.md guidance helps but does not guarantee
- Use `4` space indentation matching existing codebase
- Use `prettier` with `--tab-width 4` before committing// .claude/settings.json enforcement guarantees post-edit normalization
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --use-tabs=false --tab-width=4 --write \"$FILE\""
}
]
}
]
}
}This split keeps a fast path for conventions that merely improve quality and a separate path for controls that must not be bypassed. Style preferences and architectural notes benefit from probabilistic guidance because they interact well with user intent and existing code patterns. Security boundaries and formatting guarantees need a layer that does not ask the model for cooperation.
Boundary. Language in the requirement signals tier: should or prefer points to memory, while must or never or on every save or with zero exceptions points to settings or hooks. Items repeatedly present a technically correct CLAUDE.md phrase that still fails because probabilistic shaping cannot promise every time.
Recurring specifics. - Channel described: delivered as user message versus not system prompt. - Quality described: probabilistic versus not hard enforcement. - Contrast phrase: Settings rules are enforced by the client regardless of what Claude decides to do.
Proposal. add IMPORTANT or YOU MUST markers to strengthen guarantee in CLAUDE.md.
Why it attracts. documented tuning technique for few lines.
Why it fails. when everything is emphasized nothing is, and oversized files still lose adherence.
Proposal. move occasional formatting into a skill to guarantee enforcement.
Why it attracts. skills feel stronger.
Why it fails. skills are also model-applied knowledge, not client-enforced gates. Example of guidance versus enforcement split for a formatting guarantee: markdown # .claude/CLAUDE.md guidance helps but does not guarantee - Use 4 space indentation matching existing codebase - Use prettier with --tab-width 4 before committing json // .claude/settings.json enforcement guarantees post-edit normalization { "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "prettier --use-tabs=false --tab-width=4 --write \"$FILE\"" } ] } ] } }
- - Mutation varies enforcement hook event:
PreToolUsefor blocking versusPostToolUsefor auto-fixing, testing lifecycle understanding. - Mutation frames same probabilistic point asCLAUDE.md files are only read on first session startupormaximum effective size of 500 tokensto test load lifecycle misunderstanding.
Hard enforcement belongs in `settings.json` or hooks not in `CLAUDE.md`
Deterministic controls live in .claude/settings.json for permissions and in hooks bound to lifecycle events. Permissions use permissions.deny, permissions.allow, and permissions.ask arrays with tool patterns such as Bash(scp *), Bash(curl ), Bash(pytest tests/legacy/), or Bash(rails db:migrate*). Hooks use PreToolUse to intercept and block before execution and PostToolUse to reactively fix after execution, with matchers like Write|Edit or Bash.
// .claude/settings.json deny that deterministically blocks network commands
{
"permissions": {
"deny": [
"Bash(scp *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(nc *)"
]
}
}// .claude/settings.json ask that requires approval for migration commands
{
"permissions": {
"ask": [
"Bash(rails db:migrate*)",
"Bash(rake db:migrate*)"
]
}
}The client can refuse a tool call before it runs and can invoke a shell command after a tool succeeds regardless of model reasoning. That position in the execution path makes the rule unconditional. The examined items emphasize deny-first evaluation so a matching permissions.deny blocks even if another scope allows, and they emphasize managed settings inability to be relaxed by individual developers.
Boundary. Using settings or hooks for every stylistic preference is unnecessary weight. When a convention is broad, forward-looking, and usually followed, keeping it in CLAUDE.md or in path-scoped .claude/rules/ is appropriate. The rewarded split is CLAUDE.md for guidance, settings.json or hooks for technical enforcement where exceptions are unacceptable.
Recurring specifics. - Settings paths: .claude/settings.json as shared committed file, .claude/settings.local.json as gitignored personal override, managed-settings.json for enterprise. - Hook events: PreToolUse for blocking, PostToolUse for formatting or validation, SessionStart for injecting summary. - Example deny patterns: Bash(scp *), Bash(curl *), Bash(wget *), Bash(nc ), Bash(pytest tests/legacy/). - Example ask patterns: Bash(rails db:migrate), Bash(rake db:migrate) in permissions.ask.
Proposal. enforce clang-format by strengthening CLAUDE.md wording.
Why it attracts. minimal change.
Why it fails. still probabilistic and still occasionally skipped.
Proposal. enforce never modify config/ via CLAUDE.md instruction or .claude/rules/ glob.
Why it attracts. looks like path scoping.
Why it fails. not deterministic, correct surface is permissions.deny with path.
Proposal. use --dangerously-skip-permissions to unblock CI.
Why it attracts. quickly unblocks hang.
Why it fails. skips all permission checks, correct narrower fix is allow-listing the specific tool in settings.json. Examples of enforcement surfaces: json // .claude/settings.json deny that deterministically blocks network commands { "permissions": { "deny": [ "Bash(scp *)", "Bash(curl *)", "Bash(wget *)", "Bash(nc )" ] } } json // .claude/settings.json ask that requires approval for migration commands { "permissions": { "ask": [ "Bash(rails db:migrate)", "Bash(rake db:migrate*)" ] } }
- - Mutation swaps
PreToolUseandPostToolUseto test lifecycle understanding: block before versus fix after. - Mutation pairs enterprise managedpermissions.denywith personal~/.claude/CLAUDE.mdthat permits raw logging to test tier precedence.
Enterprise managed policy sits above all memory tiers and cannot be overridden by project or user
When an organization deploys an enterprise managed policy via managed settings, that tier occupies the top of the precedence hierarchy for both settings and memory governance. It overrides both project CLAUDE.md and user ~/.claude/CLAUDE.md, it cannot be disabled by local or project settings, and it survives session restarts as the authoritative source. Memory items from the managed tier load before user and project files.
Enterprises require non-overridable controls for secrets handling, tool blocking, and audit requirements that individual developers must not relax. Placing governance at a tier that the client treats as highest and that is delivered through MDM, Group Policy, or server-managed settings gives tamper resistance. The examined items repeatedly test this by showing a team relying on project memory and then observing a different rule appearing with no commit change to project CLAUDE.md.
Boundary. Managed policy is governance, not adherence improvement for oversized guidance. Deploying the same 900 line CLAUDE.md as a managed CLAUDE.md does not make the model follow it more reliably, it only guarantees it loads before user and project files.
Recurring specifics. - Precedence phrasing: managed policy overrides both project and user or sits at the top of the precedence hierarchy. - Delivery mechanisms: MDM, Group Policy, system-level managed-settings.json, admin console, server-managed settings. - Example conflicts: project says always use two-space indentation while managed mandates two-space indentation and bans direct file writes without confirmation.
Proposal. user memory wins because it is loaded last.
Why it attracts. maps load order to precedence.
Why it fails. managed tier defeats that intuition.
Proposal. project memory wins because it is closest to code.
Why it attracts. proximity heuristic.
Why it fails. proximity is ordering narrative, not governance hierarchy.
- - Mutation varies which control is managed: indentation, secrets handling, tool blocking for
scporcurl. - Mutation adds managedCLAUDE.mdversus managed settings distinction to test that only settings give hard guarantee.
At-path `@` import inlines another file eagerly at load time without reducing context size
A line beginning with @ followed by a path such as @./standards/naming-conventions.md inside CLAUDE.md causes Claude Code to read that target file and inline its content into the loaded memory at session start. The effect is identical to pasting the file's text directly into CLAUDE.md. The imported file is expanded during the initial load walk, not lazily on demand when a matching file is edited.
# .claude/CLAUDE.md at repository root
# Coding standards
@./standards/naming-conventions.md
@./standards/error-handling.md
@./standards/testing-requirements.md
# Architecture notes and universal commit format follow
- Commit format `[TICKET-ID] Brief description`
- Use `4` space indentation matching existing codebase# packages/api/CLAUDE.md selective import
@../../standards/api-conventions.mdSplitting a 600 or 800 line CLAUDE.md into topic files improves authoring and review ergonomics without changing runtime cost. Each topic can be owned by a different maintainer and edited independently, which avoids contradiction churn in pull requests where unrelated sections collide. The items emphasize that context cost remains the same, so modularization is an organizational win not a token win.
Boundary. If the goal is to reduce per-session context or to load conventions only when a file type is in play, @ import is the wrong tool. Path-scoped .claude/rules/ with paths frontmatter provides conditional loading, and skills provide on-demand loading by workflow trigger. Import is correct when every section genuinely applies to every session and the problem is maintainability of a monolithic file that must still always load.
Recurring specifics. - Syntax shown as @./standards/naming-conventions.md or @./standards/api.md or @./standards/error-handling.md. - File size examples: 600 lines, 650 lines, 800 lines, 900 lines, 1,400 lines for monolithic root. - Import criticized as purely organizational or cosmetic when token reduction was the stated goal. - Correct pairing: per-package CLAUDE.md files selectively import only relevant standards.
Proposal. splitting CLAUDE.md via @ imports will shrink context because imported files load on demand.
Why it attracts. modular feels like conditional.
Why it fails. imports load eagerly, same total tokens remain always loaded.
Proposal. use subdirectory CLAUDE.md files to modularize a single universal config.
Why it attracts. directory files exist.
Why it fails. they gate by directory location not by maintainability of always-loaded universal content. Example of modular import structure: markdown # .claude/CLAUDE.md at repository root # Coding standards @./standards/naming-conventions.md @./standards/error-handling.md @./standards/testing-requirements.md # Architecture notes and universal commit format follow - Commit format [TICKET-ID] Brief description - Use 4 space indentation matching existing codebase markdown # packages/api/CLAUDE.md selective import @../../standards/api-conventions.md
- - Mutation varies whether shared standards live in
standards/at repo root or.claude/standards/or vendored location. Import mechanism remains same with relative path adjusted.
Import directive is `@path` with no keyword and silently skips missing targets
The directive is the bare @ character followed immediately by a path, for example @./standards/naming.md. There is no keyword form such as @import and items that write @import are flagged as the form half the docs you'll find online write. If the referenced path does not exist at load time, Claude Code silently skips the directive and loads the remainder of CLAUDE.md normally. No error is raised and the session starts without that block.
Bare @ keeps the authoring surface minimal and lets the file remain readable Markdown. Silent skip avoids breaking CI pipelines and sessions where an optional module is conditionally present. The cost is that a typo or uncommitted file produces a silent gap in instructions that is hard to notice without diagnostics.
Boundary. Silent skip becomes a trap after a security review where the canonical file was not committed or was deleted at its source. Engineers pull latest changes but sessions still reflect old requirements because the import resolved to nothing. The correct response is that sessions started before the pull have not reloaded, or that the imported path was relative to the containing CLAUDE.md not to the project root.
Recurring specifics. - Syntax negative example: @import described as non-existent keyword, correct form is @path. - Missing target behavior: silently skipped and does not cause errors or prevent CLAUDE.md from loading. - Cause for gap: file not committed to the repository or import path relative to CLAUDE.md location versus project root.
Proposal. Claude Code raises a fatal error or refuses to start when an @ target is missing.
Why it attracts. suggests fail-fast safety.
Why it fails. session still starts with gap.
Proposal. missing target is substituted from an internal cache of last known content.
Why it attracts. suggests resilience.
Why it fails. no such cache exists for imports.
- - Mutation checks whether student verifies import path is relative to containing
CLAUDE.mdlocation rather than to project root. - Mutation tests stale session versus missing file as explanation for old requirements after pull.
Import paths resolve relative to the containing `CLAUDE.md` and support depth and code span constraints
An @ path such as @./skills/SKILL.md inside .claude/CLAUDE.md resolves relative to the directory containing that .claude/CLAUDE.md, not relative to the repository root where git was invoked. Separate documentation on import limits notes that depth of nested imports and total code span of inlined content are bounded to prevent pathological expansion.
Relative resolution keeps the import portable when the repository is checked out at different absolute paths on different machines and keeps the relationship stable if the memory file and its sibling standards directory move together. Assuming root-relative resolution breaks when the containing file lives in .claude/ and the target lives in ./standards/ beside it.
Boundary. Uncertainty flag: the reference material mentions depth and code span rules for @ imports without stating exact numeric limits or whether limits are configured via settings. Treat any specific numeric cap as uncertain official syntax until verified in https://docs.claude.com/en/docs/claude-code/memory or https://code.claude.com/docs/en/memory. For exam purposes, the testable boundary is existence of limits, not their precise values.
Recurring specifics. - Probe question: Check if the @import path is relative to the CLAUDE.md file's location or the project root. - Depth phrasing seen as recursion depth or nested imports with silent skip or truncation when exceeded.
Proposal. path resolves relative to project root regardless of containing file location.
Why it attracts. simple mental model.
Why it fails. breaks .claude/CLAUDE.md importing ./standards/ beside .claude/.
Proposal. import depth is unlimited because imports are just text inclusion.
Why it attracts. assumes pure preprocessor.
Why it fails. bounded to prevent infinite expansion via circular imports.
- - Mutation swaps skill import with standards import while preserving relativity test.
`claudeMdExcludes` in settings removes noisy discovery targets from the walk
Claude Code settings expose an exclusion field named claudeMdExcludes that suppresses specific paths from the CLAUDE.md discovery walk. Typical targets are large monorepo vendor trees, generated directories, or documentation mirrors where an accidental CLAUDE.md would inject noise into every session whose walk includes that root. The field is configured in settings at the appropriate tier, often project or managed tier, and is evaluated during the walk before concatenation.
// .claude/settings.json walk-level exclusion removes vendored tree from discovery
{
"claudeMdExcludes": [
"vendor/**",
"**/__generated__/**"
]
}# .claude/rules/ts-conventions.md rule-level negation gates authored type conventions
---
paths: ["**/*.ts", "**/*.tsx", "!**/__generated__/**", "!vendor/**"]
---
- Use explicit return types and no default exports
- Exhaustive switch handling with `never` in default branchDiscovery walks filesystem trees that may contain third party checkouts or generated SDK copies where a CLAUDE.md from an upstream template would otherwise be collected as project memory. Excluding those subtrees preserves signal and reduces token load without deleting the upstream file or requiring per-file path-scoped rules.
Boundary. Exclude is discovery control, not conditional loading control. For conventions that should apply only to certain file types wherever they appear, path-scoped .claude/rules/ with paths globs is the correct mechanism because it gates on edited file identity not on walk inclusion. For conventions bound to a subtree, directory CLAUDE.md gating is clearer than globally excluding a subtree from all discovery.
Recurring specifics. - Field name claudeMdExcludes cited alongside settings locations settings.json and managed-settings.json. - Discovery flag --add-dir interaction: excludes also apply to roots added via --add-dir. - Companion pattern for negative scoping via rules: paths: ["/*.ts", "!/__generated__/", "!vendor/"] as type glob with exclusions inside rules tier, distinct from walk exclusion.
Proposal. place exclude field inside CLAUDE.md frontmatter to suppress noisy files.
Why it attracts. keeps exclusion near guidance.
Why it fails. walk exclusion is a settings-tier field not a memory frontmatter field.
Proposal. rely on .claudeignore or ad hoc ignore file to suppress CLAUDE.md walk.
Why it attracts. gitignore mental model.
Why it fails. real discovery control is the settings field claudeMdExcludes. Example of exclude versus rule negation distinction: json // .claude/settings.json walk-level exclusion removes vendored tree from discovery { "claudeMdExcludes": [ "vendor/", "/__generated__/" ] } yaml # .claude/rules/ts-conventions.md rule-level negation gates authored type conventions --- paths: ["/.ts", "/.tsx", "!/__generated__/", "!vendor/"] --- - Use explicit return types and no default exports - Exhaustive switch handling with never in default branch
- - Mutation tests whether student confuses walk exclusion with path-scoped rule negation, both use glob syntax but apply at different stages. - Mutation adds
--add-diradded roots to see whether student knows excludes apply to those additional walks as well.
`--add-dir` extends the walk to additional roots and merges their `CLAUDE.md` chains
The --add-dir command line option adds an additional directory root to the discovery scope. Claude Code walks that added root alongside the primary working directory's walk, collecting its CLAUDE.md chain from filesystem root down to the added directory and merging the resulting memory with the primary chain. The added root's memory is concatenated additively, and skills discovered through --add-dir are treated with live change detection semantics that differ from standard session-start loading.
Working across multiple packages or repositories in a single session is common in platform work. --add-dir lets the session see conventions from a related root without moving the primary working directory or copying memory.
Boundary. The option extends discovery scope, it does not replace project memory. If a question claims that --add-dir replaces or suppresses the primary root chain, that is incorrect. If the requirement is to load an extra instructions file for one session without affecting teammates, --custom-instructions or --add-dir can serve as temporary overlays, but committed project memory remains the correct durable home for team-wide always-loaded standards.
Recurring specifics. - Flag --add-dir appears alongside discussion of session memory loading and skill live detection. - Related flag --custom-instructions described as adds additional instructions on top of those from CLAUDE.md for the session.
Proposal. --add-dir replaces CLAUDE.md for that session.
Why it attracts. suggests flag as override.
Why it fails. it adds additional root context, base instructions remain active.
Proposal. --add-dir merges another CLAUDE.md via explicit path argument.
Why it attracts. close to --custom-instructions semantics.
Why it fails. --add-dir merges walks not single file path.
- - Mutation swaps
--add-dirwith--custom-instructionsorCLAUDE_CONFIGenvironment variable fabrication to test which flag actually exists.
`CLAUDE.md` files load at session start from the walk not per turn or via watcher
Claude Code collects memory during session initialization by walking the filesystem once from root to current working directory. The resulting concatenated block is supplied as context for the session. New CLAUDE.md files created after the session has started, edits to CLAUDE.md committed mid-session, and skill file updates made while the session is running are not picked up until the session restarts, with the special case that skills loaded via --add-dir support live change detection while standard memory does not.
Single collection at start keeps context stable and avoids per-turn filesystem cost and context thrashing. Mid-session reloading would change guidance between turns and create inconsistent behavior within one conversation.
Boundary. If an engineer reports sessions still reflecting old requirements after pulling latest changes, the most likely explanation is stale session, not broken import or cached config. The fix is to restart the session after the pull.
Recurring specifics. - Phrase: loaded at session start paired with new files created mid-session require a restart. - Negative behaviors denied: live directory watching, reloads at the start of each new conversation turn. - Skill nuance: Skills loaded via --add-dir support live change detection, but standard session-start loading may need restart.
Proposal. Claude Code watches for new CLAUDE.md files and loads them as they are created.
Why it attracts. modern watcher expectation.
Why it fails. no watcher for standard memory.
Proposal. subdirectory CLAUDE.md files require explicit @import to activate at session start.
Why it attracts. confuses conditional loading with directory gating.
Why it fails. subdirectory files load automatically on walk, no import needed.
- - Mutation swaps
CLAUDE.mdstaleness with/memorydiagnostics to test whether student knows restart versus inspection is the correct remedy.
`/memory` and `/context` are diagnostic viewers that do not trigger loading
Two slash commands inspect what is already loaded. /memory lists CLAUDE.md, CLAUDE.local.md, and auto-memory locations and can open them in the editor for in-place edits. /context reports what actually loaded into the current session under a Memory files section plus token usage. Neither command causes discovery or concatenation to run again. Configuration loads automatically based on tier and working directory at session start, the commands only reveal that result.
# Developer suspects missing convention
/memory
# Inspect Memory files list for ~/.claude/CLAUDE.md, CLAUDE.md, .claude/CLAUDE.md, packages/api/CLAUDE.md
/context
# Confirm which of those files actually entered context and check token countsDiagnosis and activation are deliberately separated so that inspecting state does not mutate it. This matches the product's local-first discovery where filesystem placement is the activation mechanism. The commands are read-only lenses.
Boundary. Telling a teammate to run /memory to load the config files is a recurring wrong answer. The correct advice is to verify placement on disk and working directory, then restart the session so the walk runs again.
Recurring specifics. - Phrase: diagnostic command that shows which files are currently loaded. - Phrase: does not trigger loading or does not load, trigger, or activate anything. - Usage split: Run /context and read Memory files to verify your CLAUDE.md and CLAUDE.local.md files loaded versus exam guide naming /memory for same purpose.
Proposal. /memory loads configuration files from the hierarchy when run.
Why it attracts. name suggests memory management.
Why it fails. still diagnostic only.
Proposal. /memory clears loaded configuration and resets to defaults.
Why it attracts. suggests reset capability.
Why it fails. read-only diagnostic. Example of correct diagnostic workflow: shell # Developer suspects missing convention /memory # Inspect Memory files list for ~/.claude/CLAUDE.md, CLAUDE.md, .claude/CLAUDE.md, packages/api/CLAUDE.md /context # Confirm which of those files actually entered context and check token counts
- - Mutation replaces
/memorywith/statusor/configor/inspectas fake diagnostic command to test real command recall. - Mutation frames verification ofmanaged, project, and usertier sourcing to test that/memorydisplays all three.
Exam keying uses `/memory` while current CLI reports loaded set under `/context`
The certification guide and items written to its vintage treat /memory as the command that shows which memory files are active. The current Claude Code implementation splits the job: /memory lists and edits memory sources, /context reports the Memory files that actually entered the session alongside compression history and token counts. Both are diagnostic and neither triggers loading. Items explicitly note this split with phrasing like On the exam, answer /memory while at your actual keyboard, run /context.
Product evolved without breaking the certification contract. Rather than reissuing the guide, the implementation kept /memory for editing and introduced /context for load reporting, while the exam continues to key /memory as the diagnostic answer.
Boundary. At a real keyboard, verifying loaded files means inspecting /contextMemory files. On the test, selecting /memory when asked which built-in capability reveals active memory is correct even though it is not the full current CLI answer.
Recurring specifics. - Split note: Current Claude Code splits the job across two commands. /memory lists ... /context reports what actually loaded under Memory files. - Guide note: The exam guide predates the split and treats /memory as the command that shows which files are loaded. Give /memory as the keyed answer. Run /context at your actual keyboard. - Location phrase: under Memory files for the list that confirms CLAUDE.md and CLAUDE.local.md loaded.
Proposal. pick /status or /config or /inspect as diagnostic command.
Why it attracts. plausible command vocabulary.
Why it fails. real commands for this purpose are /memory on exam and /context in current CLI.
Proposal. claim /compact summarizes active rules after compression.
Why it attracts. compaction interacts with memory.
Why it fails. /compact manages conversation history not memory reporting.
- - Mutation varies options between
/memory,/context,/compact,/clearto probe non-reload understanding.
Project root `CLAUDE.md` survives `/compact` via re-read while nested and path-scoped rules reload lazily
When /compact summarizes a long session to free tokens, project root CLAUDE.md persists in full because Claude Code re-reads it from disk after compaction and re-injects it. The content was never part of conversation history so there is nothing for the summarizer to compress. Nested CLAUDE.md files in subdirectories and .claude/rules/ files with paths frontmatter do not automatically reappear at the moment compaction ends, they return the next time Claude reads a matching file or works inside that directory.
Always-loaded project context is treated as persistent configuration not as chat history. Re-reading it after compaction restores durable standards without relying on the summarizer to preserve detail. Lazy return for conditional guidance avoids paying context cost for rules that may not be needed in the next turns after compaction.
Boundary. An instruction that seems to vanish after /compact is often misdiagnosed as a compaction bug. The correct causes to check are whether the instruction lived in a nested CLAUDE.md that has not been re-entered or in a path-scoped rule whose glob does not currently match, or whether the instruction existed only in conversation.
Recurring specifics. - Phrase: CLAUDE.md content is treated as persistent configuration, not conversation history. When /compact summarises the conversation to free tokens, CLAUDE.md instructions remain intact. - Lazy phrasing: nested CLAUDE.md files in subdirectories and .claude/rules/ files with paths frontmatter load on demand, so they return the next time Claude reads a matching file.
Proposal. root CLAUDE.md is summarised alongside conversation and may lose specificity.
Why it attracts. compaction summarizes everything.
Why it fails. memory is re-read not summarized.
Proposal. rules are removed entirely and must be reloaded manually with /memory.
Why it attracts. manual recovery expectation.
Why it fails. automatic re-read for root, lazy return for conditional.
- - Mutation swaps root survival with skill or subdirectory survival to test that only universal always-loaded files are re-read immediately.
Path-scoped `.claude/rules/` with `paths` frontmatter loads only when matching files are in play
Files under .claude/rules/ such as testing.md, api-conventions.md, or terraform.md can carry YAML frontmatter with a paths key holding glob patterns. When the session edits or reads a file whose path matches the glob, that rule file is loaded into context for that interaction. When no matching file is in play, the rule adds no tokens. This conditional loading contrasts with CLAUDE.md and @ imports which load unconditionally every session, and with directory CLAUDE.md which gates by directory subtree rather than by glob match.
# .claude/rules/testing.md with conditional loading
---
paths: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"]
---
- Test naming uses `should` pattern with `describe` and `it`
- Use fixtures via `setupTestFixtures()` not ad hoc inline setup
- Assertion style prefers `expect(...).toEqual(...)` for collections# .claude/rules/terraform.md loads only when editing Terraform
---
paths: ["terraform/**/*", "**/*.tf"]
---
- Formatting uses `terraform fmt` with `2` space indentation
- Variable naming uses `snake_case` with `var_` prefix for inputsMany conventions are bound to file type or scattered locations not to a single directory subtree. Tests spread as /.test.ts beside components, migrations as /migrations//, Terraform as terraform// plus /.tf, Docker as /Dockerfile. Conditional loading keeps the per-session token cost proportional to what is actually being edited while still giving automatic guidance when the matching file type is touched.
Boundary. The rule applies when relevance follows the edited file identity, not the working directory. When conventions are genuinely bound to a subtree such as packages/api/ REST rules or ios/ Swift patterns, directory CLAUDE.md inside that subtree is the clearer mechanism because it matches by location rather than by pattern.
Recurring specifics. - Frontmatter key paths with glob array, cited as paths: ["/.test.ts", "/.test.tsx"] or paths: ["terraform//", "/.tf"] or paths: ["src/mobile/*/"]. - Property that without frontmatter the file loads for all sessions, with frontmatter it loads conditionally. - Token framing: loads only when matching files are in play, consuming tokens only when relevant rather than always being injected.
Proposal. nested CLAUDE.md files match via glob frontmatter.
Why it attracts. mixes directory mechanism with rule mechanism.
Why it fails. frontmatter gating belongs to .claude/rules/ not to CLAUDE.md.
Proposal. path-scoped rules grant higher precedence or block user memory.
Why it attracts. suggests rules as policy tier.
Why it fails. rules are conditional guidance, not hard precedence. Example of correct path-scoped rule: yaml # .claude/rules/testing.md with conditional loading --- paths: ["/.test.ts", "/.test.tsx", "/.spec.ts"] --- - Test naming uses should pattern with describe and it - Use fixtures via setupTestFixtures() not ad hoc inline setup - Assertion style prefers expect(...).toEqual(...) for collections yaml # .claude/rules/terraform.md loads only when editing Terraform --- paths: ["terraform//", "*/.tf"] --- - Formatting uses terraform fmt with 2 space indentation - Variable naming uses snake_case with var_ prefix for inputs
- - Mutation varies glob axis: directory subtree versus file extension versus type wildcard to test correct tier choice.
Missing `paths` frontmatter in `.claude/rules/` causes always-loaded behavior
A file placed in .claude/rules/ with no YAML frontmatter, or with frontmatter that omits the paths key, is treated as globally applicable within the project scope. It loads for every session regardless of which file is currently being edited. Adding a paths array with glob patterns changes behavior to conditional matching.
The default is broad applicability so that project-wide conventions that forgot to declare a scope still have effect while the path-scoped opt-in is explicit. This avoids silent loss of guidance when a maintainer omits frontmatter.
Boundary. If the intent is truly universal such as formatting or commit etiquette that should apply to every interaction, omitting paths is correct and the file belongs in project memory or as an always-loaded rule. If the intent is file-type-specific such as PEP 8 for /.py or React hooks for /.tsx, conditional paths is required.
Recurring specifics. - Symptom phrasing: conventions from testing.md are loading even when editing API handler files, consuming unnecessary tokens. - Fix phrasing: Add YAML frontmatter with paths: ["/.test.ts", "/.test.tsx", "*/.spec.ts"] to testing.md.
Proposal. move testing content out of .claude/rules/ into directory CLAUDE.md inside test folder.
Why it attracts. directory gating intuition.
Why it fails. test files are colocated with source in hundreds of packages, directory file would duplicate everywhere while single glob */.test.* solves it.
Proposal. add @./.claude/rules/testing.md import line in root CLAUDE.md to make loading conditional.
Why it attracts. confuses import with conditional.
Why it fails. imports load eagerly.
- - Mutation may frame glob choice as file-type versus directory list to test that type glob
/.pyis preferred over enumeratingkernels/,bindings/*style directory lists when files interleave.
Modular sharing of universal standards uses `@` import of a single canonical file not copy
When the same secure coding or naming standard must apply identically across many repositories or packages, the durable fix is to publish one canonical source file such as secure-coding-standards.md in a shared or vendored location and replace each inline copy in every CLAUDE.md with an @ reference to that single file. Every session then resolves the one current document at load time. Copying the standard at creation time produces drift where audits later find 30 repositories enforcing superseded validation and PII logging rules because each copy aged independently.
# repo-root/CLAUDE.md replaces inline standards with single reference
@./shared/secure-coding-standards.md
# Project-specific architecture follows below
- Service boundaries follow domain driven design
- Error handling uses `Result<T, E>` with explicit `error.code`# Shared file vendored into every repository via submodule or copy-on-clone
shared/
secure-coding-standards.mdCopy is cheap at write time and expensive over maintenance horizon. Each quarterly standards revision otherwise requires 30 separate pull requests to re-paste text, and an email reminder does not prevent missed copies. Reference by @ import makes the update atomic at the canonical file, so the next session after pull sees new text without per-repo edit.
Boundary. Copy via CI sync job that overwrites a delimited section in each CLAUDE.md on every merge is presented as automation fix but is still flagged as wrong relative to canonical @ import. Similarly, moving universal standards into .claude/rules/ with type globs is conditional loading, but a universal standard must be always-loaded so its scope matches apply identically.
Recurring specifics. - Scale language: roughly 30 separate service repositories, about 30 developers. - Drift evidence: revised four times last year, superseded PII-logging and conflicting validation rules. - Canonical file name: secure-coding-standards.md or shared standards directory vendored into every repository.
Proposal. keep each CLAUDE.md with clearly delimited SECURE-CODING STANDARDS section that a CI job syncs from shared repo on every merge.
Why it attracts. automation suggests consistency.
Why it fails. still copy with sync dependency, not single-source reference.
Proposal. move standards into .claude/rules/ with paths: ["/.py", "/.ts", "*/.go"].
Why it attracts. conditional by type feels scoped.
Why it fails. universal standard must load unconditionally for every file. Example of canonical import structure: markdown # repo-root/CLAUDE.md replaces inline standards with single reference @./shared/secure-coding-standards.md # Project-specific architecture follows below - Service boundaries follow domain driven design - Error handling uses Result<T, E> with explicit error.code shell # Shared file vendored into every repository via submodule or copy-on-clone shared/ secure-coding-standards.md
- - Mutation varies canonical location as shared drive, submodule, or vendored file. Mechanism remains canonical
@import.
Per-package `CLAUDE.md` should selectively import relevant standards not all standards
In a monorepo with React package, Python API package, and Go gateway package, or with packages/api and packages/frontend with deep domain knowledge per maintainer, each package directory holds its own CLAUDE.md that uses @ to pull in only the standards files relevant to that package. The API package imports API conventions, the frontend imports component rules. No duplication is stored in the package file itself.
Selective inclusion prevents duplication drift where a change to shared standards would otherwise require edits in 3 places, and it prevents irrelevant context bloat where a Python session is forced to carry React and Go standards it will never use.
Boundary. Selective import is correct when each package has distinct language-specific conventions. A single root CLAUDE.md holding all 3 sets under headers is wrong because that root loads for every package including irrelevant sections. Copying identical CLAUDE.md into each package is also wrong due to duplication.
Recurring specifics. - Example imports per package: @./standards/api.md, @./standards/testing.md per service. - Counterproposals rejected: One root CLAUDE.md with all three sets of standards under language-specific headers, Three identical CLAUDE.md files.
Proposal. one root CLAUDE.md with all standards under headers and model instructed to apply matching section per file type.
Why it attracts. single source feels simple.
Why it fails. still always-loaded for every session and relies on header inference which still bleeds.
Proposal. separate slash commands for /test-frontend and /test-backend containing rules.
Why it attracts. command encapsulation.
Why it fails. commands are on-demand invocation not ambient hierarchical context.
- - Mutation varies language mix as
PythonversusTypeScriptversusSQLversusRust, but selective per-package import remains answer.
Skills and slash commands are on-demand loading distinct from always-loaded `CLAUDE.md`
Reusable workflows that bundle instructions plus scripts, templates, and task-specific checklists belong in skills under .claude/skills/ or custom slash commands under .claude/commands/. A skill's short description may be discoverable in context, but its body loads only when the skill is invoked explicitly via /command or by intent match when the model judges it relevant. CLAUDE.md by contrast loads unconditionally at session start.
# .claude/skills/release-prep/SKILL.md frontmatter makes invocation explicit for rare workflow
---
name: release-prep
description: Prepare firmware release packet and quarterly compliance summary
disable-model-invocation: true
---Always-loaded context must be lean to preserve adherence. Detailed step-by-step guides for release packaging, asset pipeline migrations, firmware release packets, or quarterly compliance summaries that run twice a month do not justify taxing every session. On-demand loading lets the release workflow add only a small metadata footprint to non-release sessions.
Boundary. Skills become wrong tier when the workflow is frequent and universal. If the content should apply to every file type or every interaction such as always use named exports instead of default exports for TypeScript, then project memory or an unconditionally loaded .claude/rules/ file without paths restriction is correct.
Recurring specifics. - Path pairs: .claude/skills/translate-vue/SKILL.md versus .claude/commands/review-code.md versus CLAUDE.md. - Workflow examples: prepare a release involving changelog script, version bump in 3 config files, formatted release notes from template, security audit report. - Invocation detail: disable-model-invocation: true makes skill purely user-invoked.
Proposal. place translation behavior inside CLAUDE.md with conditional markdown block or context: fork frontmatter.
Why it attracts. keeps everything in memory.
Why it fails. CLAUDE.md is always-loaded so React maintenance risks generating Vue code accidentally.
Proposal. convert every topic section including universal standards into a separate skill.
Why it attracts. maximal modularity.
Why it fails. universal standards would then require explicit invocation and coverage drops. Example of per-trigger skill metadata: yaml # .claude/skills/release-prep/SKILL.md frontmatter makes invocation explicit for rare workflow --- name: release-prep description: Prepare firmware release packet and quarterly compliance summary disable-model-invocation: true ---
- - Mutation swaps
translate-vueskill withrelease-preporsecurity auditskill. Trigger mechanism stays on-demand.
Agent SDK `settingSources` defaults to no filesystem settings and must be enabled explicitly
The Claude Agent SDK runs with no filesystem settings loaded by default. That means CLAUDE.md, .claude/rules/, and skills sitting in the working directory are not picked up unless the caller sets settingSources to include project. Once enabled, the SDK respects the same filesystem sources as the CLI.
// Agent SDK query with explicit settingSources to load project memory and rules
import { query } from "@anthropic-ai/claude-agent-sdk";
const result = await query({
prompt: "Review this service against API naming conventions",
options: {
settingSources: ["project"],
cwd: "/repo/packages/api"
}
});// Setting sources including both tiers when both should apply
{
"settingSources": ["user", "project"]
}Defaulting to isolated loading makes the SDK safe for headless and multi-tenant invocations where implicit filesystem side effects would be surprising. Explicit opt-in lets callers choose which scopes to honor, for example ["user", "project"] if both personal and repository tiers should apply.
Boundary. If the caller intends project memory to be active, leaving default settingSources untouched is incorrect. Contradictory rules across user and project still concatenate additively with no hard precedence, now bounded by which sources the SDK was told to load.
Recurring specifics. - Field name settingSources paired with values user and project and list form [ user, project ]. - Behavior phrasing: By default the SDK loads no filesystem settings, so set setting_sources to include project and CLAUDE.md, rules, and skills will load. - Incorrect remedies cited: rename CLAUDE.md to AGENTS.md, clear session cache, move project CLAUDE.md into ~/.claude.
Proposal. clear SDK cache and project CLAUDE.md will load on next query call.
Why it attracts. cache invalidation mental model.
Why it fails. not a caching problem, it is opt-in flag problem.
Proposal. SDK loads user scope by default so move project file into ~/.claude/.
Why it attracts. locates file where SDK looks.
Why it fails. still need to enable project source, and relocation breaks team sharing. Example of explicit enablement: typescript // Agent SDK query with explicit settingSources to load project memory and rules import { query } from "@anthropic-ai/claude-agent-sdk"; const result = await query({ prompt: "Review this service against API naming conventions", options: { settingSources: ["project"], cwd: "/repo/packages/api" } }); json // Setting sources including both tiers when both should apply { "settingSources": ["user", "project"] }
- - Mutation varies whether SDK ignores all filesystem versus only user scope. Correct answer remains no filesystem by default unless explicitly included.
Sensitivity rule - secrets in version-controlled memory leak to API and must be environment-variable referenced
Content in CLAUDE.md and any @ imported file is sent to the Anthropic API as part of model context. Secrets written literally such as API_KEY=sk-abc123 or an MCP server connection block with auth token inline therefore leave the machine and enter version control history where they leak on public GitHub. The correct pattern is to keep secrets in .env files excluded from version control and to reference only the environment variable name from memory such as Use the TEST_API_KEY environment variable or ${INTERNAL_MCP_TOKEN} expansion in .mcp.json.
# CLAUDE.md references secret by name only
- For local test environment use the `TEST_API_KEY` environment variable
- For internal MCP server, see `.mcp.json` definition with `${INTERNAL_MCP_TOKEN}`// .mcp.json defines server shared with team while token is not committed
{
"mcpServers": {
"internal-tools": {
"command": "node",
"args": ["./mcp/internal-server.js"],
"env": {
"INTERNAL_MCP_TOKEN": "${INTERNAL_MCP_TOKEN}"
}
}
}
}Memory is documentation and context, not a credential store. The repository is shared with every developer and potentially with the model's logging surface, so literal values violate least privilege and rotate poorly. Variable-name references preserve intent while delegating value material directly to the runner environment.
Boundary. Moving sensitive notes to a separate file and not @ importing it keeps documentation value without API transmission. Encrypting sensitive sections of CLAUDE.md is wrong because CLAUDE.md must be readable to be useful and encryption would block model use. Adding [private] markers or privacy: true frontmatter is also wrong because no such marker is supported.
Recurring specifics. - Leak example: API_KEY=sk-abc123 # Use this for the local test environment committed to public GitHub. - Correct reference phrasing: Use the TEST_API_KEY environment variable rather than storing the value, or Define server in .mcp.json with ${INTERNAL_MCP_TOKEN} expansion. - Storage alternatives: .env files excluded from version control, secret managers. - Transmission consequence: Content in CLAUDE.md and @imported files is always sent to the API.
Proposal. Claude Code automatically encrypts CLAUDE.md contents so inline secret is safe.
Why it attracts. platform encryption expectation.
Why it fails. no such automatic encryption, context is transmitted.
Proposal. add [private] marker before sensitive sections so Claude Code skips them.
Why it attracts. looks like selective redaction.
Why it fails. not supported. Example of corrected sensitivity handling: markdown # CLAUDE.md references secret by name only - For local test environment use the TEST_API_KEY environment variable - For internal MCP server, see .mcp.json definition with ${INTERNAL_MCP_TOKEN} json // .mcp.json defines server shared with team while token is not committed { "mcpServers": { "internal-tools": { "command": "node", "args": ["./mcp/internal-server.js"], "env": { "INTERNAL_MCP_TOKEN": "${INTERNAL_MCP_TOKEN}" } } } }
- - Mutation swaps
CLAUDE.mdsecret with.mcp.jsontoken. Both requireenvexpansion pattern.
Token efficiency rule - conditionally relevant guidance belongs in path-scoped rules or skills not in always-loaded `CLAUDE.md`
CLAUDE.md is unconditional context read in full at the start of every session whether or not its content is relevant to the task. As always-loaded volume grows, adherence degrades because critical rules compete with hundreds of lines of noise. Guidance that matters only when editing a particular file type, directory, or during a specific workflow should therefore live in a mechanism that loads only when relevant: path-scoped .claude/rules/ with paths globs for file-type governance, directory CLAUDE.md for subtree governance, or skills for task-triggered governance.
Context cost should be proportional to task relevance. A React component session should not pay token cost for Terraform module versioning rules, and an API handler session should not pay for frontend style. Progressive loading keeps universal always-loaded set small and moves situational knowledge behind a match condition.
Boundary. If a section genuinely applies to every session such as universal commit format or repository etiquette, keeping it always-loaded is correct and no token win is expected. Splitting universally relevant load across imports is still organizational win. The opposite error is pruning a universal standard to keep it small by dropping content. Items that show 500, 650, 800, 900, or 1,400 line files and ask for maintainability fix while dropping no standard reward path-scoped .claude/rules/ with intact preservation of every standard verbatim.
Recurring specifics. - Size markers: 600 lines, 800 lines, 900 lines, 500 lines, 1,400 lines. - Adherence symptom: Claude now frequently ignores core coding standards. - Cosmetic fix flagged: Split the file into topic files and reference them all with @path imports from CLAUDE.md described as leaving total always-loaded volume unchanged. - Coverage instruction: Every existing standard is preserved verbatim in exactly one file, and the root CLAUDE.md retains only universal standards.
Proposal. condense file by stripping explanatory comments so only terse one-line rules remain.
Why it attracts. cuts line count.
Why it fails. still always-loaded for every session and removes context that improves adherence.
Proposal. move all 900 lines to a .claude/rules/ file with glob `` so it still matches broadly but lives in organized location.
Why it attracts. sounds like structural improvement.
Why it fails. glob `` matches every file so it still loads every session.
- - Mutation varies whether token waste stems from file-type guidance versus directory guidance versus workflow guidance. Correct mechanism switches between path-scoped rules, directory
CLAUDE.md, and skills accordingly.
Scope mismatch explains new teammate inconsistency and is diagnosed via `/memory` then fixed by moving to project tier
The signature failure pattern is Developer A follows team conventions perfectly, Developer B gets inconsistent or generic suggestions on same repo and same branch. Investigation consistently finds that the convention lives in Developer A's ~/.claude/CLAUDE.md or in another non-shared location such as a single engineer's personal configuration, not in the version-controlled CLAUDE.md or .claude/CLAUDE.md. Diagnosis uses the diagnostic viewer to confirm which files are currently loaded, and the fix is to move the convention into project scope and commit it so every clone receives it.
Placement is distribution. The hierarchy separates who sees what by where it lives, not by what the prose says. A paragraph telling the model to apply only when editing .py files under jobs/ still travels as always-loaded project memory if it lives in project CLAUDE.md, and it still fails to reach teammates if it lives in user memory. Only tier relocation changes the audience and the load condition.
Boundary. The pattern flips when the teammate who sees correct behavior is the one who lacked the rule. For example, a developer with verbose commit preferences in ~/.claude/CLAUDE.md sees nudges toward ruff in an unrelated client's repo where that client's reviewers reject it. There the fix is opposite: personal habits should stay in user scope while client standards stay in project scope.
Recurring specifics. - Signature phrase across many items: New team member clones the repository and Claude Code does not apply team's coding standards while other members do. - Root cause phrasing: API naming conventions are stored in Developer A's user-level CLAUDE.md (~/.claude/CLAUDE.md) rather than project-level configuration. - Diagnostic step: Ask the engineer to run the /memory command to verify which memory files are currently loaded in their session. - Fix phrasing: Moving the testing standards and fixture documentation into project-level CLAUDE.md committed to the repository, then split into focused topic files in .claude/rules/.
Proposal. developer B needs to run /memory to activate configuration.
Why it attracts. /memory is associated with memory.
Why it fails. activation is automatic by location, /memory is diagnostic.
Proposal. developer B needs to install an MCP server to access naming rules.
Why it attracts. MCP suggests missing capability.
Why it fails. naming conventions are memory, not MCP tool integration.
Proposal. reinitialize repository with /init or run /init command to bootstrap new CLAUDE.md.
Why it attracts. /init scaffolds starter file.
Why it fails. starter scaffolding does not distribute an existing misplaced convention.
- - Mutation adds secondary problem of cross-topic bleed where
18%of edits applyKotlinrules toSwiftfiles. Fix then pairs moving to project scope with path-scoped rule or directoryCLAUDE.mdaxis choice. - Mutation extends toSparkdataset conventions versus universal standards andPR templateto test that consultancy multi-client framing still reduces to tier mismatch.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| CLAUDE.md concatenation | settings.json override | CLAUDE.md concatenates from broadest to most specific, scalars do not replace, conflicts may be resolved arbitrarily. settings.json overrides layer by layer with managed above CLI above local above project above user, scalars replace, arrays concatenate. |
| User ~/.claude/CLAUDE.md | Project CLAUDE.md | User lives outside any repo and is not shared via git. Project lives in the repo and is shared on clone. The new team member not receiving instructions almost always has the rule stuck at user level. |
| At path import | At import directive | There is no @import keyword, the syntax is @path/to/file directly in the body. An option quoting @import is a distractor. |
| .claude/rules with paths frontmatter | Directory level CLAUDE.md | Path scoped rules apply to a file pattern across the entire codebase via glob. Directory level applies only to files in that single directory. Co located test files across many directories are the canonical rule case. |
| CLAUDE.md for always on rules | Skills SKILL.md for on demand workflows | CLAUDE.md loads on every session, skills load only when invoked directly or when description or paths matches intent. Universal API naming belongs in CLAUDE.md or path scoped rules, occasional checklists belong in skills. |
Traps
The tempting answer. Resolve conflicting CLAUDE.md guidance by assuming deeper scope silently overrides shallower scope.
Why it fails. Layers concatenate, both instructions sit in context and the model may pick either when they conflict. Shadowing intuition comes from most config systems but does not apply here, and treating it as configuration misses that enforcement belongs in settings.json or hooks.
What is correct. Write non conflicting guidance, use claudeMdExcludes to skip irrelevant files in monorepos, and move must hold rules to settings.json or hooks where they are enforced.
The tempting answer. Run /memory or /context to make Claude load project CLAUDE.md that is currently missing.
Why it fails. Both are diagnostic only. Loading is automatic by location and lazy trigger. Commands list known locations or report what already loaded under Memory files, they never trigger a new load.
What is correct. Place the file at the correct level, use /memory and /context to verify, and fix scope or trigger rather than invoking a diagnostic as an activation step.
The tempting answer. Drop a CLAUDE.md into each directory that contains tests to cover test files co located with source across 50 plus directories.
Why it fails. That requires one file per directory and drifts when new directories gain tests. Path scoped rules cover every matching file with one file and one glob such as star star slash star dot test dot ts.
What is correct. Create .claude/rules/testing.md with paths frontmatter carrying the test globs and verify with /context while editing a test file.
The tempting answer. Write never run rm dash rf or ask before refunds in CLAUDE.md to block destructive or sensitive tools.
Why it fails. CLAUDE.md is probabilistic and the model may misinterpret or ignore it under pressure. settings.json deny rules are enforced by the client regardless of model choice, and the exam marks the settings path as the guarantee.
What is correct. Put destructive blocks and policy gates in .claude/settings.json permissions, use CLAUDE.md only for guidance that can tolerate interpretation.
The tempting answer. Assume a session in packages/web pays context cost for packages/payments CLAUDE.md at launch because all project CLAUDE.md files load eagerly.
Why it fails. Only the walk up chain is eager. Subdirectory files below the working directory are lazy and load only when a file in that subdirectory is read, which is what makes per package rules cheap.
What is correct. Keep per package conventions in their package directory and rely on lazy loading to avoid paying for unrelated packages until touched.
Slash Commands and Skills
Create on demand slash commands as skills with correct frontmatter so they isolate noise, pre approve tools, and fire only when intended.
What you need to know
Custom commands and skills are now one mechanism. A flat Markdown file at .claude/commands/deploy.md creates slash command /deploy, and a directory at .claude/skills/deploy containing SKILL.md also creates /deploy with identical runtime behaviour. The skill path is recommended because it adds capabilities the flat alias cannot: a directory of supporting files alongside the entrypoint, automatic discovery so Claude can load the skill when its description matches the user's intent, and precedence when a skill and a command share the same name, the skill wins. Both paths support the same YAML frontmatter and both keep older .claude/commands files working unchanged.
A skill is a SKILL.md file with YAML frontmatter followed by markdown content, there is no separate skill.json descriptor, no --install-skill flag, and no dedicated npm registry for skills. The lifecycle runs through discovery where Claude Code watches .claude/skills and ~/.claude/skills and plugin skills, installation as adding a file to a discovery location, invocation either explicitly via /name or automatically when the description and intent align, execution where Claude reads the body at load time and either runs inline or as a forked subagent if context fork is set, and result return where output appears in the conversation or is returned to the main session if it ran as a fork.
Three frontmatter options are most directly tested. context fork runs the skill in an isolated subagent so verbose exploration such as file listings and dependency graphs stays contained and the main window remains clean, which is essential for codebase analysis or brainstorming workflows. allowed-tools lists tools that run without a permission prompt while the skill is active while other tools remain callable subject to normal permissions, and to actually remove a tool from the pool list it in disallowed-tools or add deny rules in settings.json, since the true security boundary is deny rules not the allow list. argument-hint is the autocomplete pattern shown when the skill is invoked without required arguments, prompting the developer for the missing values and making parameterised skills discoverable.
The distinction the exam drills is skills versus CLAUDE.md. Skills are on demand task specific workflows whose descriptions stay in context so Claude knows they exist while the full body loads only when invoked, either via /name or automatically when description or paths frontmatter matches the request. CLAUDE.md is always loaded universal standards that apply with no invocation step. The rule is to keep task specific procedures in skills and universal reference material in CLAUDE.md or path scoped rules, so universal API naming belongs in memory and a multi step analysis that runs occasionally belongs in a skill.
Distribution beyond a single repo explains the remaining mechanics. Skills are files discovered from ~/.claude/skills for personal scope, .claude/skills for shared project scope, and plugin or marketplace skills installed via /plugin with manifests in .claude-plugin/plugin.json and sources from GitHub, URL hosted marketplace.json, npm package, or local path, with extraKnownMarketplaces and enabledPlugins honored in settings.json with the same precedence as any other setting. Permission integration is the same system Claude Code uses elsewhere, with three controls: adding Skill to deny rules to disable all skills, Skill(name) or Skill(prefix star) rules to allow or deny a specific skill, and disable-model-invocation true in frontmatter to hide a single skill from automatic invocation while keeping explicit /name.
SKILL.md anatomy and what no longer exists
Every skill is a SKILL.md file whose top matter is YAML frontmatter and whose body is markdown instructions that Claude reads when the skill is loaded. Field name is the display name defaulting to directory name, description is what Claude uses to decide automatic invocation and when combined with when_to_use is capped at 1536 characters in listings, argument-hint prompts for missing args, arguments defines named positional args for dollar name substitution, and allowed-tools grants promptless use of listed tools while the skill is active.
Several invented mechanisms are distractors. There is no skill.json, no --install-skill flag, no npm registry dedicated to skills, and a flat file dropped directly into .claude/skills as review.md does not create /review. The flat file path that creates a slash command is .claude/commands/name.md, while the skills path that creates one is .claude/skills/name/SKILL.md as a directory containing the entrypoint.
Isolation, allow listing, and explicit only invocation
Forked execution is requested with context fork in frontmatter. The skill body runs in an isolated subagent context window, all verbose output stays contained there, and only a concise summary returns to the main conversation. Without the flag the output flows into the main window and degrades snippet quality for subsequent turns, which is the exam correct answer for verbose output clutters the main conversation.
Invocation control has two layers that solve different problems. disable-model-invocation true removes the skill from Claude's automatic invocation so it only runs when the developer types /name, which is correct for security audits or deploy workflows that should not auto fire. allowed-tools and disallowed-tools control tool access while the skill runs, and Skill(name) permission rules such as Skill(commit) or Skill(review-pr star) control which skills are even available to be invoked, with Skill alone disabling all skills.
Discovery locations and team sharing
Claude Code watches skill directories at multiple scopes and picks up live edits within an already known skills directory without a restart, though creating a brand new top level skills directory that did not exist at session start requires a restart. User scope at ~/.claude/skills is personal and applies to every project, project scope at .claude/skills and parent directories up to the repo root is shared via git, plugin skills extend wherever the plugin is installed, and managed policy can deploy organization wide.
The team sharing boundary mirrors CLAUDE.md. Project scoped .claude/skills and .claude/commands are the correct place for a team /review checklist so every clone receives it, while user scoped ~/.claude/skills/brainstorm holds personal workflows. The reverse choices fail, personal scope for a team command means teammates never see it, flat file in .claude/skills without a SKILL.md directory means no command is created.
Built in versus custom and interactive versus print
Built in slash commands split into fixed logic commands such as /clear, /compact, /doctor, /debug, /init, /permissions, /context, /rewind and bundled skill commands such as /batch, /code-review, and /security-review that give Claude instructions and let it orchestrate with tools. Bundled skills can produce variable output and depend on tool availability, unlike fixed commands, and most session management commands are interactive only.
In print mode with -p the distinction matters for CI. Most session management commands do not apply the same way headlessly, while user invoked skills and custom commands do work in -p, you include /skill-name directly in the prompt passed to claude -p. That is why a CI check that needs team review logic invokes /review as part of the headless prompt rather than trying to run /compact or /doctor there.
Authoritative mechanism reference
The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.
Mechanism reference: M1: The unified two-path equivalence
The Skills system collapses what were previously two separate concepts (slash commands and skills) into one registry. A flat file at .claude/commands/deploy.md and a directory at .claude/skills/deploy/SKILL.md both register a /deploy command that behaves identically at invocation time. The discovery layer scans both locations, normalizes them into the same internal command registry, and exposes them through the same slash-command picker. The body content of either form becomes the prompt instructions that Claude follows when the command runs.
The equivalence holds for the resulting behavior of the command, not for the feature set. The .claude/commands/ form is a strict subset: the same slash command appears, but it cannot carry a supporting-files directory, cannot be auto-discovered by intent matching, and loses the collision precedence that the directory form enjoys. A question that assumes the two paths differ in command behavior or availability is wrong; they do not, except through the extra capabilities the directory form attaches.
The merge exists because both surfaces answer the same need: a reusable, named block of instructions the agent executes on request. Treating them as one registry removes the cognitive overhead of remembering which folder a given workflow must live in. Backward compatibility is explicitly preserved so existing .claude/commands/ files keep working without migration.
Mechanism reference: M2: Skill is a directory with SKILL.md; command is a flat .md file
The two paths differ in on-disk shape. A command is a single flat Markdown file whose name minus extension is the command: .claude/commands/review.md becomes /review. A skill is a directory named after the command that contains a required entrypoint file called SKILL.md: .claude/skills/review/SKILL.md becomes /review. The presence of a directory plus an internal SKILL.md is what lets the skills form carry extra siblings such as a supporting-files folder.
The directory shape is the enabling constraint for the richer features. A flat file has no room for sibling assets; a directory does. By requiring a fixed entrypoint name SKILL.md, the loader always knows which file to read for the instructions, while any other files in the directory are treated as supporting material rather than instructions. The recurring literals are SKILL.md as the required entrypoint, the directory name matching the command (review), and the flat file .md extension. A proposed entrypoint name such as COMMAND.md or index.md is wrong; the fixed name is SKILL.md.
Mechanism reference: M3: A loose flat file dropped into .claude/skills/ creates no command
Because skills are directories with an internal SKILL.md, the loader only treats a directory entry under .claude/skills/ as a candidate skill. If a developer drops a flat Markdown file directly into that folder, for example .claude/skills/review.md, the scanner sees a file where it expected a directory and skips it. No /review command is registered. The file is effectively invisible to the command system.
The loader's contract is "directory under skills/ containing SKILL.md." A loose file violates the directory expectation, so it is not even inspected for frontmatter or content. This is the single most repeated trap in the tested material. The nearby opposite case is placing that same content at .claude/commands/review.md, where the flat form is exactly correct and produces /review. The boundary is purely the parent folder: flat file is valid in commands/, invalid in skills/. A directory .claude/skills/review/ without a SKILL.md also fails, but for a different reason (missing entrypoint) rather than wrong shape.
Mechanism reference: M4: .claude/skills/ is the canonical, feature-rich path; .claude/commands/ is a backward-compatible alias
Both folders yield commands, but the documentation designates .claude/skills/ as the canonical location and .claude/commands/ as an alias kept for compatibility. The canonical path is the one that gains new capabilities as the system evolves. Questions that ask for "the canonical location" point unambiguously at .claude/skills/; answers naming .claude/commands/ are marked wrong even though the command would still function.
Canonical status reflects where future features land. By steering new projects to .claude/skills/, the maintainers concentrate capability (supporting files, discovery, precedence) in one place while keeping the old path working so existing repositories are not broken. The alias is a courtesy, not a recommendation. The recurring trio is .claude/skills/ (canonical), .claude/commands/ (backward-compatible alias), and ~/.claude/skills/ (user-scoped personal).
Mechanism reference: M5: The skills path adds supporting files, automatic discovery, and name-collision precedence
The directory form unlocks three capabilities the flat alias lacks. First, supporting files: any sibling files next to SKILL.md are available to the skill as reference material, and scripts can resolve their location with the ${CLAUDE_SKILL_DIR} substitution so they work whether the skill is installed at the personal, project, or plugin level. Second, automatic discovery: the system reads a skill's description and can auto-load it when the user's intent matches, without an explicit slash invocation. Third, precedence: when a skill and a command share a name, the skill (directory form) wins. These are the reasons the canonical path is preferred.
Supporting files need a container, which only a directory provides. Auto-discovery needs structured metadata (the description) that the skills form is designed to carry. Precedence resolves ambiguity in favor of the richer form so that the feature-complete definition is the one that survives a name clash. The nearby opposite case is a command defined only in .claude/commands/: it gets none of the three capabilities. If two same-named definitions exist, one in each path, the skills form wins regardless of which was authored first.
Mechanism reference: M6: Project scope shares via version control; user scope is personal
Scope is decided by the parent directory. Anything inside the repository's .claude/ tree is project-scoped: committed to version control and propagated to every developer who clones or pulls. Anything inside the user-level ~/.claude/ tree is user-scoped: it lives on one machine, is never committed, and is invisible to teammates. This single axis, repository path versus home directory path, decides whether a command is a team asset or a personal one.
The distribution mechanism is version control. Files under the project root travel with the repo; files under the home directory do not. The system reads both trees and merges them, but only the project tree is shared. A command that "works for me but not my teammates" is the classic symptom of user-scope placement. The boundary is the leading path: .claude/ (relative to repo root) is shared; ~/.claude/ (home) is personal.
Mechanism reference: M7: The full frontmatter value space
Every SKILL.md begins with an optional YAML frontmatter block between --- markers. Most fields are optional; only description is recommended. The Agent Skills standard defines name, description, license, compatibility, metadata, and allowed-tools; every other field below is a Claude Code extension. The complete documented value space, with the value each field accepts, is:
name(optional, defaults to the directory name). The display name in skill listings. The docs note.claude/commands/files ignore this field because the command name comes from the file name.description(recommended). What the skill does and when to use it. Claude uses it to decide when to apply the skill automatically. Combined withwhen_to_use, the listing text is capped at 1,536 characters; lead with the key use case. Omitting it degrades the skill to explicit-only invocation.when_to_use(optional). Additional trigger phrases or example requests, appended todescriptionfor matching.argument-hint(optional). Autocomplete hint for expected arguments, e.g.[issue-number]. Surfaces a label in the/picker.arguments(optional). Named positional arguments for$namesubstitution in the skill body.disable-model-invocation(optional, defaultfalse). Settrueto stop Claude from auto-invoking the skill; it then only runs when you explicitly type/name. This also removes the description from context entirely.user-invocable(optional, defaulttrue). Setfalseto hide the skill from the/menu; Claude can still auto-load it. Use for background knowledge. Note: setting bothuser-invocable: falseanddisable-model-invocation: truemakes the skill unreachable.allowed-tools(optional). Tools Claude may use without a permission prompt while the skill is active. Accepts a space- or comma-separated string, or a YAML list, and supports per-tool argument patterns such asBash(git add *). It does NOT restrict the pool.disallowed-tools(optional). Tools removed from Claude's available pool while the skill is active. Accepts the same string or list format. The restriction clears when you send your next message. The deny side complement toallowed-tools; it dominatesallowed-toolsfor any overlapping tool.context(optional). Set toforkto run the skill in an isolated subagent context.agent(optional, defaultgeneral-purpose). Which subagent type to use whencontext: forkis set; built-in values includeExplore,Plan,general-purpose, or any custom subagent from.claude/agents/.background(optional, defaulttrue, requires v2.1.218+). Only applies withcontext: fork. Setfalseto wait for the forked subagent's result in the turn that invoked the skill.model(optional, defaultinherit). Model to use while the skill is active, for the rest of the current turn; accepts the same values as/modelorinherit. The session model resumes after the skill completes.effort(optional). Effort level while active:low,medium,high,xhigh,max(availability depends on model).paths(optional). Glob patterns that limit automatic activation to matching files. Accepts a comma-separated string or YAML list. Ignored in a.claude/commands/file.hooks(optional). Lifecycle hooks scoped to this skill's execution.shell(optional, defaultbash). Shell for !commanddynamic-injection blocks:bashorpowershell.mcp(optional). MCP servers scoped to the skill.license,compatibility,metadata,version(Agent Skills standard fields, indexed by the catalog rather than controlling Claude Code invocation).
A skill can be pure reference content (runs inline so Claude can apply conventions alongside the conversation) or a task (step-by-step instructions, usually invoked with /name or disable-model-invocation: true). Reference content with no task and context: fork produces no output, because the subagent receives guidelines but no actionable prompt.
Mechanism reference: M8: Pre-approving tools versus restricting them
The allowed-tools frontmatter field names tools the skill may use. In current behavior it functions as a pre-approval list: the listed tools run without a permission prompt while the skill is active, but the field does not structurally remove unlisted tools. Every other tool remains callable under the session's normal permission settings. The exam guide, however, presents allowed-tools as a restriction that removes other tools from the skill's reach, describing a read-only analysis skill configured with allowed-tools: [Read, Grep, Glob] as "cannot use Write or Bash."
The two framings converge on intent: a read-only skill should never write files or run shell commands. They diverge only when reasoning precisely about what a tool not on the list can still do. Under live behavior, a tool not named in allowed-tools is still callable subject to normal permissions; allowed-tools only suppresses the approval prompt for the named tools. The actual restriction is disallowed-tools, which removes the listed tools from Claude's available pool while the skill is active. disallowed-tools dominates allowed-tools for any tool named in both, because a removed tool cannot be approved.
The durable or org-wide boundary lives in permission deny rules (permissions.deny in settings, or /permissions), not in skill frontmatter. disallowed-tools is turn-scoped: the restriction clears when the user sends the next message, so it is a convenience for least privilege during one skill run, not a persistent lock. Skills that declare allowed-tools or hooks are treated as elevated-permission requests and require user approval before first use. A prompt instruction inside the skill body never removes a capability; only disallowed-tools plus permission settings enforce a boundary.
Mechanism reference: M9: context: fork isolates verbose skill output
The context: fork frontmatter field instructs the skill to run in an isolated subagent context. The skill's intermediate and verbose output, file trees, grep results, partial reasoning, and candidate evaluations, is produced inside that forked context and never enters the main conversation. Only the skill's returned summary reaches the main thread, preserving the main context window. The skill content becomes the prompt that drives the subagent and the forked subagent has no access to the conversation history.
Exploratory skills are noisy by nature; they enumerate, search, and weigh many options before concluding. Without isolation, all that noise consumes the finite context budget of the main conversation, degrading the quality of subsequent responses. Forking moves the noise to a side context whose budget does not compete with the main thread. The skill runs in the background by default (you keep working while it runs, and its result arrives when it completes); set background: false to wait in the invoking turn. In non-interactive mode, when background tasks are disabled, or when an earlier invocation is still running, Claude Code waits for the result regardless.
The nearby opposite case is a skill that is short and clean by design, where forking adds no value, or a skill whose whole point is to show the user the exploration rather than a summary; there, omitting context: fork is correct. A recurring distractor is to "fix context clutter" with allowed-tools or argument-hint; those change capability or input, not where output lands. Forking is the only frontmatter mechanism that relocates output.
Mechanism reference: M10: description drives the menu and automatic discovery
The description field serves two roles. In the slash-command menu it populates the tooltip or summary users see when browsing commands via /help or the / picker. In the skills system it is the text the discovery layer reads to decide whether a skill matches the user's intent and should be auto-invoked. A skill without a useful description only ever fires when the user types its name explicitly. The fallback when description is omitted is the first paragraph of the skill body, which the docs call a weaker summary than a hand-written one.
Discovery needs a searchable summary of what the skill does; description is that summary. The when_to_use field appends additional trigger phrases or example requests to widen matching. Descriptions are loaded into a context listing whose character budget scales at 1% of the model's context window; when the listing overflows, descriptions of the least-used skills are shortened or dropped, which can strip the keywords Claude needs to match. Run /doctor for an estimate of the listing's context cost. Write the description in the third person, lead with the key use case, and list the phrases users actually say.
Mechanism reference: M11: Invocation control with disable-model-invocation and user-invocable
Two fields control who may invoke a skill. disable-model-invocation: true removes the skill from automatic discovery so Claude never auto-loads it; the user must invoke it explicitly by name. It also removes the description from context entirely, which both prevents auto-loading and frees listing budget. user-invocable: false hides the skill from the / menu but lets Claude still auto-load it; use it for background knowledge that is not a meaningful command. The combination matters: disable-model-invocation: true plus user-invocable: false makes the skill unreachable (hidden from the menu and from automatic loading), so choose one or the other.
The exam repeatedly tests the "destructive workflow" framing: a deploy or data-migration skill should set disable-model-invocation: true so it only runs on explicit /name, never because Claude judged it relevant. The default (neither field set) means both the user and Claude can invoke it, the description stays in context, and the body loads on invocation.
Mechanism reference: M12: Path-scoped auto-loading with the paths field
The paths frontmatter field is a real, documented capability (resolving the forensics OPEN flag). It holds glob patterns that limit a skill's automatic activation to matching files. When you edit or operate on a file that matches a declared path pattern, the skill is loaded automatically. This is a file-driven variant of intent matching distinct from the description match. In a .claude/commands/ file, paths is ignored because command names come from file names, not frontmatter.
Path-scoped loading matters because some skills are relevant due to which file is open, not due to the wording of the request. A paths: ["/.test.ts"] declaration attaches the skill to test files so the expertise appears exactly when a matching file is in play. Glob correctness is a common pitfall: /.tsx and *.tsx are not the same pattern, because the latter matches only the working directory, not nested ones. For global conventions that apply to matching files automatically, .claude/rules/ is the better home than a skill; paths is for skill auto-loading, not for applying a rule to every matching file unconditionally.
Mechanism reference: M13: Model and effort overrides
The model field overrides the model used while the skill is active, for the rest of the current turn; it accepts the same values as /model or the literal inherit (which matches the main conversation's model and is the default). The session model resumes after the skill completes. The effort field sets the reasoning effort while active, with the value space low, medium, high, xhigh, max (availability depends on the model in use). Override these only when justified; most skills should inherit from the session. Both fields are skill-scoped conveniences, not session-level configuration, which lives in settings.json or the model: line of CLAUDE.md.
Mechanism reference: M14: Argument passing through $ARGUMENTS and named arguments
Inside a command or skill body, the token $ARGUMENTS is replaced at invocation time with whatever the user typed after the command name. This lets a single file accept variable input, a target file, a diff, a version string, without separate definitions per value. The placeholder is resolved by the command runner before the instructions are passed to the agent. When the body does not reference $ARGUMENTS, extra text is simply ignored. The arguments frontmatter field provides named positional arguments for $name substitution in the skill body, a richer alternative to the single $ARGUMENTS catch-all.
A second substitution mechanism is dynamic context injection: a line of the form !command is executed and replaced with its output before Claude sees the skill content, so instructions arrive grounded in live data. For example !git diff HEAD inlines the current working-tree diff. Injected commands never prompt for permission; if a permission check returns anything other than allow, the invocation aborts. A matching ask or deny rule still aborts regardless of allowed-tools. Scripts bundled with a skill resolve their own location with ${CLAUDE_SKILL_DIR} so they work at any install level.
Mechanism reference: M15: Name-collision precedence
Two distinct precedence rules apply. Across the two file shapes, when a skill (directory form) and a command (flat form) share a name, the skill takes precedence and becomes the active /command; the directory form is preferred because it is the canonical, feature-complete definition. Resolution is by form, not by timestamp, so "the later one wins" is wrong, and "both run concatenated" is wrong.
Across scopes, the current documentation resolves the forensics contradiction (Rule 25): "Across levels, enterprise overrides personal, and personal overrides project." So for a same-named skill living in both ~/.claude/skills/ and the project's .claude/skills/, the personal one wins locally. A skill at any of these levels also overrides a bundled skill with the same name (but not the bundled skill's aliases, so a project code-review skill does not run when you type the bundled alias /review). Plugin skills use a plugin-name:skill-name namespace and cannot collide.
Because the documented personal-over-project rule surprised earlier guidance that called same-name collisions "ambiguous," the defensible recommendation that satisfies either reading is the distinct-name pattern: give the personal variant a different name (e.g. /commit-mine mirroring a shared /commit) so the two definitions never collide. The forensics OPEN flag about a skills list in CLAUDE.md is not corroborated by current documentation; the real surfaces are the .claude/skills/ discovery plus skillOverrides in settings, not a frontmatter list inside CLAUDE.md.
Mechanism reference: M16: The boundary between always-loaded memory, conditionally loaded rules, and on-demand skills
Three layers carry guidance, and the exam tests the distinction directly. CLAUDE.md loads always, for every session, automatically, with no invocation step; it holds universal standards that must shape every action. .claude/rules/ files are conditionally loaded: path-scoped rules load as always-on context alongside matching files, combining the automaticity of CLAUDE.md with path specificity it lacks. Skills are on-demand: their description is always in context so Claude knows they exist, but the full body loads only when invoked, explicitly or by intent or path matching.
The decision test is: is this a universal standard that must apply to every action, or a task-specific procedure invoked occasionally? Universal standards (naming conventions, export styles, prohibited patterns) go in CLAUDE.md or .claude/rules/; task-specific procedures (code review checklists, analysis routines, brainstorming templates) go in skills. A convention that applies only to a specific file type or path belongs in path-scoped .claude/rules/, not in global CLAUDE.md and not in a skill. context: fork does not convert a CLAUDE.md procedure into on-demand behavior; forking only relocates a skill's output and never suppresses CLAUDE.md, which loads for every session regardless.
Mechanism reference: M17: Agent Skills standard fields versus Claude Code extensions
The SKILL.md format follows the open Agent Skills standard, which works across multiple AI tools, so a skill written for Claude Code can be portable to other compliant runtimes. The standard defines a small set of fields: name, description, license, compatibility, metadata, and allowed-tools. Everything else documented above (when_to_use, argument-hint, arguments, disable-model-invocation, user-invocable, disallowed-tools, context, agent, background, model, effort, paths, hooks, shell, mcp) is a Claude Code extension. This split matters when a skill is meant to travel: a portable skill should rely only on the standard fields for its core behavior, while Claude Code-specific conveniences (isolation, invocation control, path scoping, model and effort overrides) enhance behavior only inside Claude Code.
The allowed-tools field is marked experimental in the standard and its support varies across implementations, but in Claude Code it is fully supported as a pre-approval mechanism. The metadata field carries arbitrary catalog metadata (author, tags, and so on) that is indexed by registries but does not control Claude Code invocation. A skill that depends on context: fork or paths to function correctly is, by definition, Claude Code-specific and will not gain those behaviors in a runtime that implements only the standard. Authors distributing a skill broadly should therefore keep the invocation-critical logic in the body and the standard fields, and treat Claude Code extensions as progressive enhancement.
Ownership map
This section assigns each guarantee in the Skills system to the layer that owns it, so a candidate can reason about where a behavior is enforced.
- File shape and discovery: owned by the Claude Code CLI loader. It enumerates
.claude/skills/,.claude/commands/,~/.claude/skills/, pluginskills/directories, and.claude/skills/up the parent chain to the repo root. The loader decides that a directory withSKILL.mdis a skill and that a flat.mdfile incommands/is a command. - Frontmatter parsing: owned by the CLI. Malformed YAML loads the body with empty metadata, so
/namestill works but nodescriptionis available for matching; run with--debugto see the parse error, orclaude plugin validateon the skills directory (v2.1.233+) to find unparseable files. - Invocation decision (explicit vs automatic): the
descriptionis loaded into context by the CLI, but the intent match is performed by the model, which decides whether a skill applies to the current request.disable-model-invocation: trueanduser-invocable: falseare CLI-enforced gates on that decision. - Tool pre-approval and denial:
allowed-toolsanddisallowed-toolsare interpreted by the CLI's permission layer, but the baseline approval for every other tool is governed by the session's permission settings (permissions.allow/ask/deny), which is a config layer above the skill. A matching ask or deny rule in settings overridesallowed-tools. - Subagent fork execution: owned by the CLI plus the agent runtime.
context: forkplusagentselects the subagent type (model, tools, permissions); the forked subagent runs in the background by default and applies its edits outside session checkpoints, so/rewinddoes not undo them. - Scope distribution: owned by the filesystem and version control. Placement under
.claude/vs~/.claude/decides sharing; git propagates project files on clone or pull. No registration step exists; placement and filename are sufficient. - Durable security boundaries: owned by
settings.jsonpermission deny rules and managed settings, not by skill frontmatter.disallowed-toolsis turn-scoped and clears on the next message.
Version and terminology currency
Several behaviors the exam guide describes as current are stable, but the field set has grown. The merge of custom commands into skills is current product behavior; the exam guide's v1.0 framing still separates "slash commands and skills" as if distinct systems, while the product has unified them. A candidate should answer using the unified model: both paths produce one /command.
Key version milestones that affect this task: context: fork arrived in v2.1.0; disallowed-tools shipped in v2.1.152 (2026-05-27), making the deny-side complement to allowed-tools real rather than the settings-only workaround older docs describe; skillOverrides gained the "off" state that also hides a skill from Remote Control and Agent SDK command lists in v2.1.199; /doctor became a bundled skill (still typable when disableBundledSkills is on) in v2.1.205; the background field for forked skills defaulted to background in v2.1.218; and claude plugin validate over a skills directory requires v2.1.233.
Terminology drift to watch: the canonical location is .claude/skills/ today, but many older examples and some community posts treat .claude/commands/ as primary. The exam guide's "three critical frontmatter options" (context: fork, allowed-tools, argument-hint) are the ones a question will key on, yet the live field list also includes description, when_to_use, disallowed-tools, disable-model-invocation, user-invocable, model, effort, paths, agent, background, hooks, shell, and arguments. A candidate should not treat description as optional even though the docs label it "recommended," because it is the discovery hook.
Official versus community divergence
Where community material contradicts Anthropic documentation, documentation wins. The divergences that matter for this task:
allowed-toolsas restriction versus pre-approval. Community and exam-guide posts describeallowed-toolsas restricting the skill to the listed tools ("cannot use Write or Bash"). The official documentation states it pre-approves listed tools without a prompt but every other tool remains callable. A candidate should answer "restriction" on the exam (that is the exam-correct framing) but understand the live pre-approval model and that the real boundary isdisallowed-toolsor permission deny rules.
- Existence of
disallowed-tools. Older documentation and some community posts say tool blocking must be done in settings only. As of v2.1.152,disallowed-toolsis a documented frontmatter field that removes tools from the pool. The exam guide's "live field list" already includes it. Treatdisallowed-toolsas real and current.
- A
skillsfrontmatter list insideCLAUDE.md. One forensics item implies such a list enabling automatic skill use, with omission making a skill command-triggered only. This is not corroborated by current documentation; the real control surfaces aredisable-model-invocationin the skill andskillOverridesin settings. Mark thisnot independently confirmedfor any claim thatCLAUDE.mdcarries a skill list.
- Scope precedence for same-named skills. Earlier guidance called same-name collisions across scopes "ambiguous and unpredictable." Current documentation states personal overrides project (enterprise overrides personal). The safe, always-correct answer is the distinct-name pattern, which avoids the collision entirely regardless of which rule applies.
- Documentation domains. Our lessons cite
code.claude.com/docs/en/...; the verified URL list usesdocs.claude.com/en/docs/.... Both resolve to the same current docs; the verified list is preferred for citations.
- Added-directory skills loading. An
--add-dirdirectory grants file access rather than configuration discovery, but skills and commands are an explicit exception: Claude Code loads.claude/skills/and.claude/commands/from each added directory automatically, alongside the project skills. This only applies when theprojectsetting source is enabled (the default); in--safe-modenone of the three (skills, commands, subagents) load from added directories. By contrast,CLAUDE.mdfrom an added directory is NOT loaded by default and requiresCLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1. A candidate should not assume that adding a directory exposes its skills only after an opt-in, the way it does forCLAUDE.md.
Beyond the task statement
The reference page omits several adjacent topics our lessons cover that a candidate may face on related questions. Each is listed with its slug and why it matters for Task 3.2.
- The
CLAUDE.mdhierarchy and golden rules (configuration). Task 3.2 tests the boundary between always-loadedCLAUDE.mdand on-demand skills; the companion lesson explains the four-levelCLAUDE.mdchain (global, project, local, directory), the concatenation-not-override rule, the@pathimport syntax, and the six golden rules. Understanding thatCLAUDE.mdloads for every session is what makes the "skills load on demand" contrast stick. - Path-scoped rules via
.mdc(the.claude/rules/system,claude-code-mdc-config). When a convention applies automatically but only to matching files,.claude/rules/is the correct home, not a skill and not globalCLAUDE.md. This is the middle layer between always-on and on-demand that the reference page hints at but does not name. - Built-in slash command lifecycle (
claude-code-slash-commands). Most built-in commands run fixed CLI logic; a smaller set are bundled skills (/code-review,/batch,/debug,/doctor) that give Claude instructions and let it orchestrate the work with its own tools. Knowing that custom commands are now defined viaSKILL.md(not a separate descriptor) and that most session-management commands are interactive-only while user-invoked skills work in-pmode prevents scoping mistakes. The lesson maps commands across the session lifecycle: setup (/init,/memory,/mcp,/agents,/permissions), mid-task (/plan,/model,/effort,/context,/compact), parallel work (/agents,/tasks,/background,/batch), pre-ship (/diff,/code-review,/review,/security-review), between sessions (/clear,/resume,/branch), and recovery (/rewind,/doctor,/debug,/feedback). The/compactversus/cleardistinction matters:/compactsummarizes history to free context and keeps what matters, while/clearstarts fresh on a new task and only reloadsCLAUDE.md.
- Distribution beyond a single repo (
claude-code-skill-management). For sharing wider than one repository, Claude Code uses a plugin and marketplace system managed with the/plugincommand; a skill folder with a.claude-plugin/plugin.jsonmanifest becomes a plugin that can bundle skills alongside agents, hooks, and MCP servers. There is no--install-skillCLI flag and no skill-specific package registry; distribution is via git for project-local skills or the plugin and marketplace system for wider sharing. Managed (organization-wide) deployment goes through managed settings. - Skills as reusable instruction sets and the Skills-versus-Projects-versus-Custom-Instructions contrast (
claude-101-skills). This beginner lesson frames Skills as saved, task-level workflows, distinct from always-on Custom Instructions and topic-level Projects. It reinforces the "skills are modular, shareable, task-specific" framing the exam expects, though it concerns the claude.ai product rather than Claude Code and is explicitly not part of the CCA-F exam weighting. - Permission integration and distribution (
claude-code-skill-management). Skills plug into the same permission system as everything else: deny theSkilltool to disable all skills, useSkill(name)orSkill(name *)rules to allow or deny specific skills, and distribute via git (project-local) or the plugin and/pluginsystem (wider sharing). TheskillOverridessetting controls visibility from settings instead of editing a shared skill's frontmatter. - Hooks as enforcement (
configuration). For guarantees that must hold regardless of what a skill does, hooks (PreToolUse/PostToolUse) and permission deny rules are the durable layer; skill frontmatterdisallowed-toolsis a turn-scoped convenience by comparison.
Worked production examples
These five examples are connected: a team builds a /review checklist (project-shared), a personal /brainstorm analysis skill (isolated, read-only), a /deploy task skill (explicit only), a /create-migration command with arguments, and a single SKILL.md showing the full frontmatter value space. Each block proves a specific mechanism and notes its failure boundary. A sixth example extends the picture to monorepo and plugin scoping, where directory-qualified names and plugin namespaces resolve collisions that the simpler two-path model hides. Together they cover every file shape, every documented frontmatter field, argument passing, isolation, tool pre-approval, and the cross-level precedence rules the exam tests.
Worked production examples: Example 1: A skill directory with its entrypoint and supporting files
A project-wide code-review skill needs a checklist file it can reference. The directory shape is what enables that supporting file. The ${CLAUDE_SKILL_DIR} substitution lets the skill find its own assets at any install level.
.claude/skills/review/
SKILL.md
checklist.md---
name: review
description: Run the team code review checklist against staged changes. Use when the user asks to review, requests a PR check, or types /review.
argument-hint: "<branch or 'staged' for uncommitted changes>"
allowed-tools:
- Read
- Grep
- Glob
- Bash(git diff *)
---# review body
Read the checklist at `${CLAUDE_SKILL_DIR}/checklist.md` and work through each item:
1. Error handling: are new code paths covered?
2. Test coverage: do new functions have tests?
3. API naming: do exported names follow the project convention?
4. Secrets: are any hardcoded credentials present?
Apply the checklist to the diff supplied in `$ARGUMENTS`.This proves the directory form carries a sibling checklist.md that a flat .claude/commands/review.md could not. Failure boundary: if you instead write .claude/skills/review.md (a loose flat file), no /review command is registered at all, because the loader expects a directory containing SKILL.md.
Worked production examples: Example 2: A flat command file
A simple team command that needs no supporting files can stay flat. The filename minus extension becomes the command name, and $ARGUMENTS carries runtime input.
# .claude/commands/standup.md
---
description: Summarize yesterday's and today's work from git history for a standup update
argument-hint: "<since-branch, e.g. main>"
---
Write a standup update from the git history since `$ARGUMENTS`:
- What I completed (merged or committed)
- What is in progress
- Blockers (failing tests, open reviews)This proves that .claude/commands/standup.md creates /standup and that $ARGUMENTS receives the typed text after the command name. Failure boundary: the flat form cannot carry a supporting-files directory, cannot be auto-discovered by description matching in the same way the skills form can, and loses name-collision precedence to a same-named skill. Also, paths and name in this frontmatter are ignored.
Worked production examples: Example 3: Full frontmatter with every documented field
This single SKILL.md shows the complete documented value space in one place. In practice you would set only the fields a skill needs; the example is intentionally exhaustive for reference.
---
# SKILL.md frontmatter - full documented value space
name: security-audit # optional; defaults to the directory name
description: >- # recommended; drives discovery and the menu tooltip
Scan the codebase for vulnerability patterns and report findings.
Use when the user asks for a security review, types /security-audit,
or mentions OWASP, secrets, or injection.
when_to_use: >- # optional; appended to description for matching
"security review, find secrets, check for SQL injection,
audit dependencies for CVEs"
argument-hint: "<directory or module to scan, e.g. src/auth>" # optional autocomplete hint
arguments: # optional named positional args for $name substitution
- target
- severity
disable-model-invocation: false # optional; true forces explicit /name only
user-invocable: true # optional; false hides from / menu but Claude can auto-load
allowed-tools: # optional; pre-approves without prompt (does NOT restrict)
- Read
- Grep
- Glob
- Bash(git log *)
disallowed-tools: # optional; removes tools from the pool while active
- Write
- Edit
context: fork # optional; run in an isolated subagent
agent: Explore # optional; subagent type when context: fork
background: false # optional; only with context: fork; wait in the invoking turn
model: inherit # optional; override model for the skill turn
effort: high # optional; low|medium|high|xhigh|max
paths: # optional; glob patterns limiting auto-activation
- "**/*.ts"
- "**/*.tsx"
hooks: # optional; lifecycle hooks scoped to this skill
PreToolUse:
- matcher: Bash
hooks:
- type: command
command: scripts/guard.sh
shell: bash # optional; shell for !`command` blocks
mcp: # optional; MCP servers scoped to the skill
- security-scanner
license: MIT # Agent Skills standard field (catalog metadata)
compatibility: "claude-code>=2.1.0"
metadata:
author: platform-team
---
# security-audit body
Scan `$target` for the patterns in `${CLAUDE_SKILL_DIR}/patterns.md` at `$severity` severity.This proves every documented field and its shape. Failure boundary: name and paths are ignored inside a .claude/commands/ file; model and effort are skill-scoped and resume to the session after the skill; disallowed-tools clears on the next message, so it is not a durable boundary; setting both user-invocable: false and disable-model-invocation: true makes the skill unreachable.
Worked production examples: Example 4: Argument passing through named arguments and $ARGUMENTS
A migration generator shows three substitution styles: the $ARGUMENTS catch-all, named $name arguments from the arguments field, and a !command dynamic-injection block.
# .claude/commands/create-migration.md
---
description: Generate a database migration scaffold for the given change
argument-hint: "<migration name, e.g. add_user_roles>"
arguments:
- name
---
Generate a migration scaffold for `$name`.
Latest committed tag: !`git describe --tags --abbrev=0`
Use the convention from our migration guide and write the file as
`migrations/$(date +%Y%m%d)_$name.sql`. The user typed: $ARGUMENTSThis proves that $name resolves from the arguments list, $ARGUMENTS carries the full typed text, and !git describe ... is replaced with live output before Claude sees the body. Failure boundary: if the body never references $ARGUMENTS, typed text is ignored rather than errored; dynamic-injection commands never prompt for permission, but a matching ask or deny permission rule still aborts the invocation regardless of allowed-tools.
Worked production examples: Example 5: A skill that pre-approves tools and runs isolated
A personal, verbose codebase-analysis skill that must be read-only and must not clutter the main conversation combines allowed-tools (pre-approval) with context: fork (isolation) and agent: Explore (read-only subagent).
---
name: brainstorm
description: >-
Brainstorm design alternatives for a feature area and report the tradeoffs.
Use when the user asks to explore options, weigh approaches, or types /brainstorm.
context: fork
agent: Explore
background: false
allowed-tools:
- Read
- Grep
- Glob
argument-hint: "Provide a feature description or codebase area to explore"
---# brainstorm body
Explore `$ARGUMENTS` and produce three candidate designs with tradeoffs.
Read widely; do not modify any file. Return a concise summary of the options.This proves the canonical analysis-skill recipe: context: fork keeps the verbose file listings and excerpts out of the main conversation, agent: Explore supplies a read-only tool set, and allowed-tools: [Read, Grep, Glob] pre-approves those three so the exploration runs without permission prompts. The skill lives at ~/.claude/skills/brainstorm/SKILL.md so it is personal and never distributed via git. Failure boundary: allowed-tools does not make the skill read-only by itself; if you needed a hard guarantee that nothing is written, add disallowed-tools: [Write, Edit, Bash] (turn-scoped) or a permission deny rule (durable). Without context: fork, the exploration output lands in the main context and degrades later responses.
Worked production examples: Example 6: Monorepo and plugin scoping with directory-qualified names
A monorepo needs a deploy skill that is specific to one package and must not shadow the root deploy. The nested directory form gives a directory-qualified name automatically.
apps/web/.claude/skills/deploy/
SKILL.md
.claude/skills/deploy/
SKILL.md
my-plugin/skills/deploy/
SKILL.md# apps/web/.claude/skills/deploy/SKILL.md
---
name: deploy
description: Deploy the web app package to its preview environment. Use when changing files under apps/web.
context: fork
allowed-tools:
- Bash(npm run deploy:web *)
---# my-plugin/skills/deploy/SKILL.md (plugin namespace)
---
name: my-plugin:deploy
description: Deploy via the shared platform plugin pipeline.
---This proves three scoping facts at once. First, a nested skill under apps/web/.claude/skills/deploy/ becomes available when Claude works on files in that package and appears under the qualified name /apps/web:deploy; typing the unqualified /deploy still runs the project-root skill, and Claude Code also invokes the nested variant when its directory holds the files in play. Second, a plugin skill at my-plugin/skills/deploy/ is namespaced as /my-plugin:deploy and cannot collide with personal, project, or enterprise skills. Third, the precedence across levels is enterprise, then personal, then project, so a same-named personal deploy wins locally over the project one, while an enterprise deploy would override both. Failure boundary: a nested skill is not loaded at startup; it loads the first time Claude reads or edits a file inside that subdirectory and stays available for the rest of the session. A flat .claude/commands/deploy.md at the root loses name-collision precedence to the directory-form skill of the same name.
Build exercise material
These steps are verifiable: each has an observable outcome that proves the mechanism worked. They follow the reference page's build exercise but add the discovery and security checks the exam rewards.
Step 1. Create a project-scoped /review command in .claude/commands/review.md containing a team code review checklist, with frontmatter description and argument-hint. Observable outcome: a file exists at .claude/commands/review.md in the repository; running /review in Claude Code triggers the checklist; the command appears for any developer who clones the repository because the file is committed. Why: project-scoped commands are shared via git, so every developer gets them on clone.
Step 2. Create a personal /brainstorm skill at ~/.claude/skills/brainstorm/SKILL.md with context: fork in the frontmatter. Observable outcome: a SKILL.md file exists under the home directory; the skill is available only in your sessions; a colleague or fresh clone without your home directory config does not see /brainstorm. Why: user scope is personal and never version-controlled.
Step 3. Add allowed-tools to the brainstorm skill restricted to Read, Grep, and Glob. Observable outcome: the frontmatter now includes the list; under current behavior those three tools run without a permission prompt, while any other tool still follows normal permission settings. If the goal is a hard read-only guarantee, also add disallowed-tools: [Write, Edit, Bash], noting it clears on the next message. Why: allowed-tools pre-approves; disallowed-tools removes.
Step 4. Add argument-hint to the brainstorm skill: "Provide a feature description or codebase area to explore". Observable outcome: invoking /brainstorm without arguments shows the hint in the picker; supplying a target removes the prompt. Why: argument-hint surfaces the expected input at invocation time.
Step 5. Verify scoping boundaries. Observable outcome: /review works in any clone of the repository; /brainstorm works only in your session. A colleague or fresh clone without your home directory config does not see /brainstorm as an available command. Why: this confirms the project-versus-user axis the exam repeatedly tests.
Step 6. Invoke the brainstorm skill against a codebase area and confirm the verbose output does not appear in the main conversation. Observable outcome: the main conversation shows a concise summary; the file listings, code excerpts, and analysis notes are not visible in the main history. Subsequent responses remain high quality because the context window is not filled with exploration output. Why: context: fork runs the skill in an isolated subagent whose output returns only as a summary.
Step 7. Convert /review to the canonical skills form to gain discovery and precedence. Move the body to .claude/skills/review/SKILL.md, add a precise description, and confirm /review now auto-triggers when a request matches the description. Observable outcome: typing a request that matches the description auto-loads the skill; a same-named flat command (if it existed) would lose on collision. Why: the directory form adds automatic discovery and name-collision precedence.
Step 8. Check the description listing budget with /doctor. Observable outcome: /doctor reports the skill listing's context cost and flags if descriptions are being truncated. Trim description and when_to_use so the key use case leads, since each entry's combined text is capped at 1,536 characters. Why: a shortened or dropped description can strip the keywords Claude needs to match the skill.
Troubleshooting and anti-patterns
The official skills documentation lists concrete failure modes. Each maps to a mechanism above and to a distractor the exam reuses.
Skill not triggering. If Claude does not use a skill when expected: check that the description includes the keywords a user would naturally say; verify the skill appears when you ask "What skills are available?"; try rephrasing the request to match the description; and invoke it directly with /name if it is user-invocable. If the frontmatter YAML is malformed, Claude Code loads the body with empty metadata, so /name still works but there is no description to match against. Run with --debug to see the parse error, or run claude plugin validate on the skills directory (v2.1.233+) to find files whose frontmatter does not parse.
Skill triggers too often. If Claude uses a skill when you do not want it: make the description more specific, and add disable-model-invocation: true if you only ever want manual invocation. The fix for over-triggering is almost always in the description, not in turning off model invocation entirely (unless that is genuinely the intent).
Skill descriptions are cut short. Claude Code loads a listing of skill names and descriptions into context so the model knows what is available. The listing always contains every skill name, but when you have many skills the descriptions are shortened to fit the listing's character budget, which can strip the keywords the model needs. The budget scales at 1% of the model's context window and drops descriptions starting with the least-invoked skills. Run /doctor for an estimate, or raise the budget with skillListingBudgetFraction in settings or the SLASH_COMMAND_TOOL_CHAR_BUDGET environment variable. Free budget by setting low-priority entries to "name-only" in skillOverrides, or by trimming description and when_to_use (each entry's combined text is capped at 1,536 characters).
Skill not appearing after a move. A skill from .claude/commands/ or a nested .claude/skills/ in an added directory is not watched live, so after you add or edit such a file there, restart the session. Live change detection covers SKILL.md text under the main skills locations without a restart; creating a top-level skills directory that did not exist when the session started requires a restart.
Fabricated keys and surfaces. A stable class of wrong answers invents configuration keys or surfaces that do not exist: override: true (to make a personal skill supersede a project one), auto_run: false and hidden: true (to disable auto-invocation), a commands array in .claude/config.json, a commands entry in settings.json, and registering commands inside CLAUDE.md. Each names the right intent with a mechanism that does not exist. The real tools are positional scoping (paths), disable-model-invocation, and the documented frontmatter set.
Scope inversion. Placing a team command in user scope, or a personal command in the project, is attractive because the command "works for me," masking that it never distributes. The project-versus-user axis decides sharing; no registration step changes it.
Mislocated guidance. Putting a task workflow in CLAUDE.md, or a universal standard in a skill, exploits confusion between the always-loaded and on-demand layers. CLAUDE.md loads every session; a skill loads only when invoked. A standard buried in a never-invoked skill is skipped; a procedure forced into CLAUDE.md bloats every session.
Feature misattribution. Assigning context: fork the job of gating behavior, allowed-tools the job of routing output, or argument-hint the job of opening a child session. Each field is credited with a neighboring field's function; the exam reuses these as distractors.
Troubleshooting and anti-patterns: Nested and plugin-scoped skills
Skills also load from nested .claude/skills/ directories below the working directory. When Claude reads or edits a file in a subdirectory, skills from that subdirectory's .claude/skills/ become available, letting a monorepo package provide its own skills that apply when working on that package even if the session started at the repo root. If a nested skill shares a name with another skill, both stay available: the nested one appears under a directory-qualified name such as apps/web:deploy, and its description says which directory it applies to. Typing /deploy runs the project-root skill; typing /apps/web:deploy runs the nested variant explicitly. When you invoke the unqualified name, Claude Code appends the directory-qualified variants to its context with an instruction to also invoke any variant whose directory holds the files being worked on.
Plugin skills use a plugin-name:skill-name namespace, so they cannot conflict with personal, project, or enterprise skills. Adding a .claude-plugin/plugin.json to a skill folder loads it as a plugin named <name>@skills-dir, so it can bundle agents, hooks, and MCP servers alongside the skill. In a project's .claude/skills/, this requires accepting the workspace trust dialog first. Enterprise skills sit above personal and project skills; a deploy skill in both ~/.claude/skills/ and the project's .claude/skills/ resolves to the personal one locally, while an enterprise deploy would override both. The precedence order across levels is enterprise, then personal, then project.
A synced skill (enabled for a claude.ai account) whose name matches any other command is skipped so the other command runs; Claude Code reserves the names of its own built-in commands and bundled skills even when they are unavailable. Name comparison ignores case, spacing, and invisible characters, and treats compatibility forms such as fullwidth letters as their plain equivalents, so a synced Commit cannot load beside a local commit.
Mechanism and API surface
- Two paths to one slash command
- .claude/commands/name.md and .claude/skills/name/SKILL.md both create /name with identical runtime, skill path supports supporting files and auto discovery and wins on same name, both use same frontmatter and both are project or user scoped.
- SKILL.md frontmatter schema
- name, description plus when_to_use capped at 1536 chars, argument-hint, arguments for dollar name substitution, disable-model-invocation true for explicit only, allowed-tools for promptless tools, context fork for isolation.
- Lifecycle Discover then Invoke then Execute
- Discover by watching skills directories, install as adding file to a discovery location, invoke explicitly via /name or automatically via description match, execute inline or as forked subagent then return result to main session.
- Discovery locations and live change rule
- ~/.claude/skills personal, .claude/skills project shared via git, plugin skills wherever installed, managed policy org wide, edits within known directories take effect live, brand new top level directory needs restart.
- Permission integration for skills
- Deny rule Skill disables all, Skill(name) or Skill(prefix star) allows or denies a specific skill, disable-model-invocation true hides single skill from auto invocation, allowed-tools grants tools without prompt while skill is active.
- Print mode rule for skills
- Most built ins are interactive only, custom skills and user invoked skills work in -p headless mode by including /skill-name in the prompt, which is the CI valid invocation.
The decision rules in play
Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.
The unified two-path equivalence
The Skills system collapses what were previously two separate concepts into a single mechanism. A file at .claude/commands/deploy.md and a directory at .claude/skills/deploy/SKILL.md both produce a /deploy command that behaves identically at invocation time. The discovery layer scans both locations, normalizes them into the same internal command registry, and exposes them to the user through the same slash-command picker. The content of either form becomes the prompt instructions that Claude follows when the command is run.
The merge exists because both surfaces answer the same user need: a reusable, named block of instructions that the agent executes on request. Treating them as one registry removes the cognitive overhead of remembering which folder a given workflow must live in. The system deliberately preserves backward compatibility so that existing .claude/commands/ files keep working without migration, while the newer directory form unlocks richer features.
Boundary. The equivalence holds for the resulting behavior of the command, not for the feature set. The .claude/commands/ form is a strict subset: the same slash command appears, but it cannot carry a supporting-files directory, cannot be auto-discovered by intent matching, and loses the collision precedence that the directory form enjoys. The nearby opposite case is any question that assumes the two paths differ in command behavior or availability - they do not, except through the extra capabilities the directory form attaches.
Recurring specifics. The recurring identifiers are the two directory names .claude/commands/ and .claude/skills/, the entrypoint filename SKILL.md, and the command name deploy derived from the path segment. The sentence "both paths create the same /command" recurs across the tested material as the anchoring claim.
Proposal. the .claude/commands/ form is deprecated and produces a different, limited command.
Why it attracts. deprecation is a common lifecycle move.
Why it fails. backward compatibility is explicitly preserved; the alias still works.
When it would be right. only if a question specifically asks about the canonical recommended location, where .claude/skills/ is preferred.
Proposal. commands and skills are unrelated systems that must be configured separately.
Why it attracts. the names differ.
Why it fails. the unified registry means a skill and a command of the same name resolve into one entry.
When it would be right. never, under the current unified model.
- A mutation replaces the command name (
review,deploy-check,lint,migration-guide) but the rule is unchanged. Another mutation asks whether.claude/commands/still works after the merge - the answer stays yes.
Skill is a directory with SKILL.md; command is a flat .md file
The two paths differ in on-disk shape. A command is a single flat Markdown file whose name (minus extension) is the command: .claude/commands/review.md becomes /review. A skill is a directory named after the command that contains a required entrypoint file called SKILL.md: .claude/skills/review/SKILL.md becomes /review. The presence of a directory plus an internal SKILL.md is what lets the skills form carry extra siblings such as a supporting-files folder.
The directory shape is the enabling constraint for the richer features. A flat file has no room for sibling assets; a directory does. By requiring a fixed entrypoint name SKILL.md, the loader always knows which file to read for the instructions, while any other files in the directory are treated as supporting material rather than instructions.
Boundary. The boundary is the file versus directory distinction at the same path level. The nearby opposite case is a flat file placed directly under .claude/skills/ - which looks like it should work but does not (covered in Rule 3). Another nearby case is a directory placed under .claude/commands/ - the loader does not expect directories there, so .claude/commands/review/SKILL.md is not the command form.
Recurring specifics. The recurring literals are SKILL.md as the required entrypoint, the directory name matching the command (review), and the flat file .md extension. The phrase "one directory per skill, named after the command, with SKILL.md as the required entrypoint" recurs.
Proposal. a skill can be a single .md file inside .claude/skills/.
Why it attracts. it mirrors the command form.
Why it fails. the skills loader requires a directory containing SKILL.md; a loose file is ignored.
When it would be right. if the file were instead placed under .claude/commands/, where the flat form is correct.
Proposal. the entrypoint may be named COMMAND.md or index.md.
Why it attracts. conventions vary across tools.
Why it fails. the required, fixed name is SKILL.md.
When it would be right. never under the current spec.
- A mutation asks for the entrypoint name specifically (
SKILL.mdversusindex.mdversuscommand.md). Another swaps the example command name. The rule is stable across all of them.
A loose flat file dropped into .claude/skills/ creates no command
Because skills are directories with an internal SKILL.md, the loader only treats a directory entry under .claude/skills/ as a candidate skill. If a developer drops a flat Markdown file directly into that folder, for example .claude/skills/review.md, the scanner sees a file where it expected a directory and skips it. No /review command is registered. The file is effectively invisible to the command system.
The loader's contract is "directory under skills/ containing SKILL.md". A loose file violates the directory expectation, so it is not even inspected for frontmatter or content. This is the single most repeated trap in the tested material: people reason by analogy from .claude/commands/ and assume the flat form works in both places.
Boundary. The nearby opposite case is placing that same content at .claude/commands/review.md, where the flat form is exactly correct and produces /review. The boundary is purely the parent folder: flat file is valid in commands/, invalid in skills/. Another nearby case: a directory .claude/skills/review/ without a SKILL.md also fails, but for a different reason (missing entrypoint) rather than wrong shape.
Recurring specifics. The recurring example is .claude/skills/review.md versus .claude/commands/review.md. The phrase "a flat file placed directly inside .claude/skills/ does not create a command" is the canonical warning. The fix is always to wrap the file in a directory and rename it SKILL.md.
Proposal. a flat file in .claude/skills/ creates the command but silently ignores its frontmatter.
Why it attracts. partial truth about frontmatter handling makes it plausible.
Why it fails. the file is skipped entirely, not partially read.
When it would be right. never; the shape is rejected before frontmatter parsing.
Proposal. the command is created but only for the user who wrote the file.
Why it attracts. scoping confusion.
Why it fails. scoping is irrelevant because the file is never registered at all.
When it would be right. never for a loose skills file.
- A mutation uses a different filename (
brainstorm.md,deploy.md) in the wrong folder. A mutation asks whether moving it tocommands/fixes it - yes. The rule is invariant to the command name.
.claude/skills/ is the canonical, feature-rich path; .claude/commands/ is a backward-compatible alias
Both folders yield commands, but the documentation designates .claude/skills/ as the canonical location and .claude/commands/ as an alias kept for compatibility. The canonical path is the one that gains new capabilities as the system evolves. Questions that ask for "the canonical location" point unambiguously at .claude/skills/; answers naming .claude/commands/ are marked wrong even though the command would still function.
Canonical status reflects where future features land. By steering new projects to .claude/skills/, the maintainers concentrate capability (supporting files, discovery, precedence) in one place while keeping the old path working so existing repositories are not broken. The alias is a courtesy, not a recommendation.
Boundary. The nearby opposite case is any question phrased as "which location is recommended" or "which is canonical" - the answer is .claude/skills/ even though .claude/commands/ is accepted as functional. If the question asks only whether .claude/commands/ works, the answer is yes; if it asks which is canonical or preferred, the answer is .claude/skills/.
Recurring specifics. The recurring trio is .claude/skills/ (canonical), .claude/commands/ (backward-compatible alias), and ~/.claude/skills/ (user-scoped personal). The label "canonical project-scoped skills location" is attached to .claude/skills/.
Proposal. .claude/commands/ is the canonical location because it came first.
Why it attracts. historical primacy.
Why it fails. canonical is a forward-looking recommendation, not a historical one.
When it would be right. never, under the unified model.
Proposal. .claude/rules/ is the skills location.
Why it attracts. it shares the .claude/ prefix and a configuration role.
Why it fails. .claude/rules/ is for path-specific convention files, not skills.
When it would be right. for always-on file-type conventions, not skills.
- A mutation asks for the user-scoped canonical location instead, flipping the answer to
~/.claude/skills/. A mutation combines scoping and canonicality: "canonical project-scoped" stays.claude/skills/.
The skills path adds automatic discovery, a supporting-files directory, and name-collision precedence
The directory form unlocks three capabilities the flat alias lacks. First, supporting files: any sibling files next to SKILL.md are available to the skill as reference material. Second, automatic discovery: the system can read a skill's description and auto-load it when the user's intent matches, without an explicit slash invocation. Third, precedence: when a skill and a command share a name, the skill (directory form) wins. These are the reasons the canonical path is preferred.
Supporting files need a container, which only a directory provides. Auto discovery needs structured metadata (the description) that the skills form is designed to carry. Precedence resolves ambiguity in favor of the richer form so that the feature-complete definition is the one that survives a name clash.
Boundary. The nearby opposite case is a command defined only in .claude/commands/: it gets none of the three capabilities. If two same-named definitions exist, one in each path, the skills form wins regardless of which was authored first. The boundary is clear: the extra features are conditioned on the directory shape.
Recurring specifics. The three features are stated as a list: supporting-files directory, automatic discovery, and precedence. The collision rule "the skill wins" recurs. The description field is the hook that discovery reads.
Proposal. .claude/commands/ also supports a supporting-files directory alongside the .md file.
Why it attracts. symmetry assumption.
Why it fails. the flat file has no sibling container by design.
When it would be right. never for the commands alias.
Proposal. when a skill and command collide, the one authored later wins.
Why it attracts. last-writer-wins is common.
Why it fails. the resolution is by form (skill wins), not by timestamp.
When it would be right. never under the stated precedence.
- A mutation asks which feature the commands alias lacks (answer: the three capabilities). Another asks what happens on collision (answer: skill wins). The rule is stable.
Project scope (.claude/) is shared through version control; user scope (~/.claude/) is personal
Scope is determined by the parent directory. Anything inside the repository's .claude/ tree is project-scoped: it is committed to version control and propagated to every developer who clones or pulls. Anything inside the user-level ~/.claude/ tree is user-scoped: it lives on one machine, is never committed, and is invisible to teammates. This single axis - repository path versus home directory path - decides whether a command is a team asset or a personal one.
Version control is the distribution mechanism. Files under the project root travel with the repo; files under the home directory do not. The system reads both trees and merges them, but only the project tree is shared. The distinction is the backbone of every sharing question in this task.
Boundary. The nearby opposite case is a command that "works for me but not my teammates" - the classic symptom of user-scope placement. The boundary is the leading path: .claude/ (relative to repo root) is shared; ~/.claude/ (home) is personal. A mutation that wraps the same file in one versus the other flips the answer completely.
Recurring specifics. The recurring pair is .claude/skills/ (project, shared via git) and ~/.claude/skills/ (user, personal). The phrases "shared via version control" and "not version-controlled" recur as the defining contrast. The clone-or-pull distribution story is the canonical framing.
Proposal. placing a command in ~/.claude/commands/ and sharing it via a wiki link reaches the team.
Why it attracts. human sharing feels equivalent.
Why it fails. it is not automatic, not version-controlled, and not inherited on clone.
When it would be right. never for a team-wide requirement; it only satisfies a personal-use goal.
Proposal. project scope requires registering the command in CLAUDE.md or a config file.
Why it attracts. explicit registration feels necessary.
Why it fails. mere placement under .claude/ is sufficient; no registration step exists.
When it would be right. never under the current model.
- A mutation swaps the required audience (personal versus team) and flips the correct path. Another mutation adds frontmatter to test whether configuration changes the scope - it does not; scope is purely positional.
The scope pattern is uniform across CLAUDE.md, commands/skills, and rules
The project-versus-user scoping rule is not special to commands; it is a global convention that applies identically to CLAUDE.md, to commands and skills, and to .claude/rules/. Project-level lives under the repository's .claude/; user-level lives under ~/.claude/. Memorizing this one pattern answers a whole family of scoping questions across the domain, because the same axis governs every configuration surface.
A consistent scoping axis reduces the mental model to a single decision: is this asset meant to travel with the repo or stay on one machine? Applying it uniformly means a developer who understands command scoping automatically understands CLAUDE.md and rules scoping.
Boundary. The nearby opposite case is assuming a command-specific rule differs from the CLAUDE.md rule. It does not. The boundary is the leading path in every case. Where things differ is not scope but loading behavior: CLAUDE.md loads always, while skills load on demand (Rules 16-18).
Recurring specifics. The recurring contrast is .claude/ (project, shared) versus ~/.claude/ (user, personal), applied to CLAUDE.md, commands/, skills/, and rules/. The phrase "the scoping pattern is consistent across Claude Code" recurs.
Proposal. commands use a different scoping directory than CLAUDE.md.
Why it attracts. commands feel like a separate subsystem.
Why it fails. the .claude/ versus ~/.claude/ axis is shared.
When it would be right. never for scope; only the loading semantics differ.
Proposal. user-scoped settings must live in ~/.claude/settings.json to affect commands.
Why it attracts. settings files are real.
Why it fails. user-scoped commands live in ~/.claude/commands/ or ~/.claude/skills/; settings files govern behavior, not command placement.
When it would be right. for permission settings, not for command location.
- A mutation asks the scope of
.claude/rules/(project) versus~/.claude/rules/(user). Another asks whether the same uniformity applies to skills - yes.
Team-wide commands must live inside the repository, not in a home directory
For a command to reach every developer automatically, it must be part of the repository so that cloning or pulling delivers it. The project-scoped location is .claude/commands/ or .claude/skills/ inside the repo. A developer who creates the command only in their own home directory has a working command locally but has shipped nothing to the team; new clones will never see it.
Distribution is performed by the version control system, not by the agent. The agent reads whatever files exist in the checked-out tree. If the file is not in the tree, no clone contains it. This is why "place it in the repo and commit it" is the universal correct answer for shared commands.
Boundary. The nearby opposite case is a personal command that should NOT be shared - there the home directory is correct (Rule 9). The boundary is intent: shared with team implies repo placement; personal implies home placement. A command that works for its author but not teammates is the diagnostic signature of home-directory placement.
Recurring specifics. The recurring command names are /review, /deploy-check, /lint, /migration-guide, and /standup. The recurring fix phrase is "move it to .claude/commands/ in the repo" or "commit it to .claude/skills/". The clone and pull distribution story recurs.
Proposal. define the command inline in the root CLAUDE.md.
Why it attracts. CLAUDE.md is committed and shared.
Why it fails. CLAUDE.md is for always-loaded standards, not command definitions, and a command embedded there is not a slash command.
When it would be right. for a universal convention, not a reusable command.
Proposal. register the command in a .claude/config.jsoncommands array.
Why it attracts. explicit registration feels natural.
Why it fails. no such configuration surface exists for commands.
When it would be right. never.
- A mutation changes the command's purpose (
/scaffold-component,/run-lint,/generate-tests) but the placement rule is unchanged. A mutation asks whether committing fixes the issue - yes.
User-scoped commands are personal and never distributed through git
A command placed under ~/.claude/commands/ or ~/.claude/skills/ belongs to one user on one machine. It is available across that user's sessions and projects but is not part of any repository, so it is never pushed, pulled, or cloned. It satisfies a personal-productivity need without affecting teammates.
The home directory is outside every repository, so version control never touches it. The agent merges user-scope configuration on top of project-scope configuration, but the user-scope file remains local. This is exactly what a developer wants for a private shortcut.
Boundary. The nearby opposite case is a team command mistakenly placed here, which then fails to reach anyone else (Rule 8). The boundary is purely the leading path: ~/.claude/ is personal by definition. If the requirement is "every developer who clones," user scope is automatically wrong.
Recurring specifics. The recurring examples are a personal /brainstorm skill and a personal exploration shortcut. The phrase "personal and not version-controlled" recurs. The home-directory path ~/.claude/skills/ is the canonical personal location.
Proposal. user-scoped commands can be shared by pushing the home directory contents into a wiki.
Why it attracts. manual distribution is conceivable.
Why it fails. it is not automatic and not version-controlled; the requirement of effortless team sharing is unmet.
When it would be right. never for a team-wide need.
Proposal. user-scoped commands require each developer to copy a file locally, which is acceptable for a team.
Why it attracts. teams do share files informally.
Why it fails. the question's "automatically on clone" requirement is violated.
When it would be right. only when the requirement is personal use, not team sharing.
- A mutation asks whether a personal command is visible to a fresh clone - no. Another asks whether the same file in
.claude/becomes shared - yes, by moving it.
context: fork isolates verbose skill output into a sub-agent context
The context: fork frontmatter field instructs the skill to run in an isolated sub-agent context. The skill's intermediate and verbose output - file trees, grep results, partial reasoning, candidate evaluations - is produced inside that forked context and never enters the main conversation. Only the skill's returned summary reaches the main thread, preserving the main context window.
---
description: "Analyse a feature area of the codebase and report structure, patterns and risks"
context: fork
allowed-tools:
- Read
- Grep
- Glob
argument-hint: "Provide a feature description or area of the codebase to analyse"
---Exploratory skills are noisy by nature; they enumerate, search, and weigh many options before concluding. Without isolation, all that noise consumes the finite context budget of the main conversation, degrading the quality of subsequent responses. Forking moves the noise to a side context whose budget does not compete with the main thread.
Boundary. The nearby opposite case is a skill that is short and clean by design, where forking adds no value and may even hide useful intermediate steps the user wants to see. The boundary is the verbosity of the output: noisy, exploratory skills benefit; terse, single-step skills do not need it.
Recurring specifics. The recurring scenario is codebase analysis and brainstorming, both described as producing "extensive" or "verbose" output. The recurring fix phrase is "add context: fork." The field appears in YAML frontmatter at the top of SKILL.md.
Proposal. add allowed-tools restricting to Read and Grep to fix context clutter.
Why it attracts. restricting tools reduces some output.
Why it fails. it limits capability but does not move output out of the main context; verbose output still lands in the main thread.
When it would be right. when the goal is a security boundary, not context isolation.
Proposal. add argument-hint so the developer must supply a target first.
Why it attracts. it changes invocation flow.
Why it fails. it prompts for input but does nothing about where output goes.
When it would be right. when the problem is missing arguments, not context pollution.
- A mutation applies the same fix to a documentation generator producing 200+ lines per endpoint - still
context: fork. Another mutation offers "move it to.claude/commands/" as the fix - wrong, because the commands alias also lacks automatic isolation unless frontmatter is present, and the file shape change does not address context.
Without context: fork, verbose skill output pollutes the main context window
When a skill lacks context: fork, its output is written directly into the main conversation history. For exploratory skills this means long listings, excerpts, and reasoning traces accumulate in the context window, consuming tokens that subsequent turns need. The degradation is cumulative: the more the skill runs, the worse later responses become.
The main conversation context is a shared, finite budget. Every token the skill emits there is a token unavailable to later reasoning. Forking is the only frontmatter mechanism that relocates that output; nothing else (tool restrictions, argument hints) changes where output lands.
Boundary. The nearby opposite case is deliberate, wanted verbosity - a skill whose whole point is to show the user the exploration, not just a summary. There, omitting context: fork is correct. The boundary is user intent: hide the noise versus show the process.
Recurring specifics. The recurring symptom is "forces frequent /compact calls mid-task." The recurring cause is missing context: fork on a verbose skill. The recurring remedy is adding the field to the skill's frontmatter.
Proposal. the main context is automatically protected, so nothing needs configuring.
Why it attracts. automatic protection would be convenient.
Why it fails. isolation is opt-in via context: fork; by default output lands in the main thread.
When it would be right. never under the current model.
Proposal. allowed-tools controls where output goes.
Why it attracts. tools and output feel linked.
Why it fails. tools govern capability, not output routing.
When it would be right. never for this symptom.
- A mutation describes the same clutter but attributes it to a missing
argument-hint- wrong, because the symptom is output location, not missing input. The rule holds regardless of command name.
allowed-tools pre-approves listed tools in current behavior but is treated as a restriction on the exam
The allowed-tools frontmatter field names tools that the skill may use. In current behavior it functions as a pre-approval list: the listed tools run without a permission prompt while the skill is active. The exam guide, however, presents allowed-tools as a restriction that removes other tools from the skill's reach. A read-only analysis skill configured with allowed-tools: [Read, Grep, Glob] is described in the tested material as "cannot use Write or Bash."
---
allowed-tools:
- Read
- Grep
- Glob
---The two framings converge on intent: a read-only skill should never be writing files or running shell commands. Whether the field pre-approves or restricts, the safe effect is the same for the typical case. The divergence matters only when reasoning precisely about what a tool not on the list can still do.
Boundary. (Uncertain official syntax: the tested material presents both the pre-approval model and the restriction model without reconciliation. Treat allowed-tools as the exam's expected "restrict to these tools" answer, but note that live Claude Code treats it as pre-approval and disallowed-tools as the true restriction. Flag this when writing authoritative guidance.) The nearby opposite case is a skill that must be allowed to call anything; there, omitting allowed-tools leaves normal permission settings in force.
Recurring specifics. The recurring list is Read, Grep, Glob for read-only analysis. The recurring statement is "limits the skill to file write operations" or "restricts to read-only tools." The field name allowed-tools is the tested identifier.
Proposal. allowed-tools is the real security boundary that removes tools.
Why it attracts. it reads like a firewall.
Why it fails. under live behavior it only pre-approves; the boundary is disallowed-tools (Rule 13). For the exam, though, it is accepted as the restriction answer.
When it would be right. as the exam's expected answer, but not as the live boundary.
Proposal. a prompt instruction inside the skill body replaces allowed-tools.
Why it attracts. self-restraint seems sufficient.
Why it fails. instructions do not remove a capability; the model can still call disallowed tools.
When it would be right. never for an enforced boundary.
- A mutation swaps the required tool set (read-only versus write-only). Another asks whether
allowed-toolsalone prevents shell execution - exam says yes, live says no (needsdisallowed-tools). The rule is stable in exam terms.
The real security boundary is disallowed-tools and permission settings, not allowed-tools
If the goal is to guarantee a skill cannot use certain tools, the enforced mechanism is disallowed-tools (an explicit deny list) together with the normal permission settings. allowed-tools only influences whether a listed tool prompts for approval; it does not structurally remove unlisted tools. A true security boundary therefore lives in the deny list and the permission policy, not in the allow list.
Allow lists answer "what may run without asking"; they do not answer "what is forbidden." Deny lists answer the latter. Security boundaries must be expressed as denials plus the surrounding permission framework, because an allow list leaves everything not mentioned still callable under normal settings.
Boundary. The nearby opposite case is a low-stakes convenience skill where pre-approval via allowed-tools is exactly what is wanted and a hard deny is unnecessary. The boundary is intent: convenience pre-approval versus enforced prohibition.
Recurring specifics. The recurring field name is disallowed-tools. The recurring contrast is "pre-approves" versus "restricts." Permission settings are referenced as the governing layer beneath skill frontmatter.
Proposal. allowed-tools is sufficient to enforce read-only.
Why it attracts. it is the tested field.
Why it fails. it pre-approves, it does not deny.
When it would be right. for the exam's simplified model, but not for a true boundary.
Proposal. the skill body can forbid tools by instruction.
Why it attracts. instructions shape behavior.
Why it fails. instructions are not enforcement.
When it would be right. never for a guaranteed boundary.
- A mutation asks for "the actual security boundary" - answer
disallowed-toolsplus permission settings. Another frames it as exam-versus-live behavior. Stable.
argument-hint prompts for required parameters and surfaces a label in the picker
The argument-hint frontmatter field provides a short label describing the argument a command or skill expects. When the user invokes the command without supplying arguments, the picker displays this hint so they know what to type. It improves the developer experience by making inputs explicit rather than relying on memory.
---
argument-hint: "Specify the module path to analyse (e.g., src/api/auth)"
---Commands and skills often need a target - a diff, a file pattern, a version string, a module path. Without a hint, users invoke with no arguments and receive a generic result, or must read the body to guess the input. The hint surfaces the expectation at the point of invocation.
Boundary. The nearby opposite case is a command that genuinely needs no argument; there, argument-hint is unnecessary. The boundary is whether an input is required or merely optional. A mutation supplying the argument still works; the hint only acts when arguments are absent.
Recurring specifics. The recurring example is argument-hint: "migration name (e.g. add_user_roles)" and argument-hint: "<version>". The field name argument-hint is the tested identifier. The symptom it fixes is "Claude shows no hint about what argument to supply."
Proposal. context: fork opens a child session that prompts the user.
Why it attracts. forking changes session behavior.
Why it fails. it isolates output, it does not surface an argument label in the picker.
When it would be right. when the problem is context clutter, not missing argument guidance.
Proposal. renaming the file to create-migration-NAME.md encodes the placeholder.
Why it attracts. filename suggests the argument.
Why it fails. the command name is derived from the filename; a placeholder token is not a real hint mechanism.
When it would be right. never as a supported pattern.
Proposal. allowed-tools: Bash signals the expected argument type.
Why it attracts. tool choice hints at input.
Why it fails. tools govern execution, not argument prompts.
When it would be right. never for this purpose.
- A mutation changes the expected argument (version string, module path, migration name). Another asks which field surfaces the label - always
argument-hint.
The description field drives the menu tooltip and automatic skill discovery
The description frontmatter field serves two roles. In the slash-command menu it populates the tooltip or summary users see when browsing commands, typically via /help. In the skills system it is the text the discovery layer reads to decide whether a skill matches the user's intent and should be auto-invoked. A skill without a useful description only ever fires when the user types its name explicitly.
---
description: "Generate a migration script for the given schema change"
---Discovery needs a searchable summary of what the skill does. The description is that summary. Without it, the auto-invocation path has nothing to match against, so the skill degrades to explicit-only even though it could be automatic.
Boundary. The nearby opposite case is a skill that is intentionally explicit-only via disable-model-invocation: true; there a weak description is acceptable because auto-invocation is disabled anyway (Rule 21). The boundary is whether automatic discovery is desired.
Recurring specifics. The recurring phrase is "Claude has nothing to match your request against." The fallback when description is omitted is "the first paragraph of the skill body," noted as a weaker summary. The field name description is the tested identifier.
Proposal. description is purely documentation and affects nothing at runtime.
Why it attracts. docs fields often do nothing.
Why it fails. it drives both the menu and discovery.
When it would be right. never; it is load-bearing.
Proposal. omit description and rely on the skill body's first paragraph, which is equivalent.
Why it attracts. the fallback exists.
Why it fails. the fallback is explicitly a worse summary than a hand-written one.
When it would be right. only as a degraded fallback, not as a best practice.
- A mutation asks which field populates the
/helptooltip -description. Another asks what discovery reads - alsodescription. Stable.
Skills load on demand; CLAUDE.md loads always
A skill's full body is loaded only when the skill is invoked, either explicitly by name or automatically by intent matching. Its description is always in context so the agent knows the skill exists, but the heavy instructions stay dormant until needed. CLAUDE.md, by contrast, is loaded into context for every session automatically, with no invocation step.
Loading everything always would flood the context window. Skills are task-specific and infrequent, so deferring their body to invocation time preserves context. CLAUDE.md holds universal standards that should apply to every action, so it must be present from the start.
Boundary. The nearby opposite case is a convention that should apply to every edit - that belongs in CLAUDE.md or .claude/rules/, not a skill (Rules 17-18). The boundary is frequency and universality: always-on versus occasional.
Recurring specifics. The recurring contrast is "skills are invoked on demand" versus "CLAUDE.md is always loaded." The phrase "the full skill body loads only when invoked" recurs.
Proposal. skills are always loaded like CLAUDE.md.
Why it attracts. both are configuration.
Why it fails. skills defer their body to invocation.
When it would be right. never; only the description is always present.
Proposal. CLAUDE.md requires an explicit invocation step.
Why it attracts. commands need invocation.
Why it fails. CLAUDE.md is automatic.
When it would be right. never.
- A mutation asks about
.claude/rules/loading - also always-on, reinforcing the distinction. Stable.
Task-specific workflows belong in skills; universal standards belong in CLAUDE.md
The decision of where to put guidance follows a single test: is this a universal standard that must shape every action, or a task-specific procedure invoked occasionally? Universal standards - naming conventions, export styles, prohibited patterns - go in CLAUDE.md (or .claude/rules/). Task-specific procedures - code review checklists, analysis routines, brainstorming templates - go in skills, invoked on demand.
CLAUDE.md is the always-loaded layer, so it is the correct home for rules that must never be skipped. Skills are the on-demand layer, so they are the correct home for procedures that would clutter context if loaded every session. Mixing them causes either gaps (standards buried in a skill that is never invoked) or bloat (procedures forced into every session via CLAUDE.md).
Boundary. The nearby opposite case is a convention that applies only to a specific file type or path - that belongs in path-scoped .claude/rules/, not in global CLAUDE.md and not in a skill (Rule 18). The boundary is universality: every action versus specific files versus occasional task.
Recurring specifics. The recurring wrong placement is "put the review checklist in CLAUDE.md." The recurring right placement is "the review checklist is a skill." The recurring universal example is "all generated TypeScript must use named exports." The rule phrase "do not put task-specific procedures in CLAUDE.md" recurs.
Proposal. put the code-review workflow in CLAUDE.md so it is never forgotten.
Why it attracts. never-forgotten feels safe.
Why it fails. it forces the workflow into every session and is the wrong loading model.
When it would be right. only if the workflow were a universal standard, which it is not.
Proposal. put a universal export convention in a skill so developers invoke it before writing code.
Why it attracts. invocation feels intentional.
Why it fails. it relies on memory and is skippable, violating "applies automatically."
When it would be right. never for a universal standard.
- A mutation changes the convention (named exports, no default exports, error handling) but the placement stays
CLAUDE.md. Another flips to a task workflow, which stays a skill.
Path-specific always-on conventions belong in .claude/rules/
When a convention should apply automatically but only to files matching a path pattern - for example test files, or files under a specific directory - the correct home is a path-scoped file under .claude/rules/. These load as always-on context alongside matching files, combining the automaticity of CLAUDE.md with path specificity that CLAUDE.md lacks.
Global CLAUDE.md is too broad for file-type-specific rules; a skill is too manual. Path-scoped rules fill the middle: always loaded, but only where relevant. This keeps the context lean while still enforcing the convention without an invocation step.
Boundary. The nearby opposite case is a truly universal convention - that stays in CLAUDE.md. The boundary is path specificity: applies everywhere versus applies to matching files. A convention with no path qualifier belongs in CLAUDE.md.
Recurring specifics. The recurring location is .claude/rules/. The recurring contrast is "path-scoped always-on" versus "global always-on (CLAUDE.md)" versus "on-demand (skill)." The example "test files must follow a pattern" recurs.
Proposal. put path-specific conventions in a skill invoked before editing.
Why it attracts. invocation feels controlled.
Why it fails. it is skippable and not automatically applied to matching files.
When it would be right. never for an automatic convention.
Proposal. put them in CLAUDE.md.
Why it attracts. always-loaded.
Why it fails. it over-applies to every file, not just matching ones.
When it would be right. only for universal conventions.
- A mutation changes the file type (test files, API files). Another asks whether
.claude/rules/is for skills - no, it is for conventions. Stable.
Skills can auto-invoke when their description matches user intent
Beyond explicit invocation by name, a skill whose description matches what the user is asking can be loaded automatically by the agent. The discovery layer reads the description, compares it to the current request, and pulls in the skill when there is a fit. This is why a well-written description matters: it is the hook for automatic behavior.
Auto-invocation lets the agent apply relevant packaged expertise without the user memorizing every skill name. The matching is driven by the description text, so a vague or missing description means the skill is invisible to automatic loading.
Boundary. The nearby opposite case is a skill with disable-model-invocation: true, which opt out of automatic loading and requires explicit invocation (Rule 21). The boundary is the presence and quality of description plus the disable-model-invocation flag.
Recurring specifics. The recurring phrase is "Claude picks up skills whose description matches the user's intent." The field description is the matching key. The flag disable-model-invocation is the opt-out.
Proposal. skills only ever run when explicitly typed.
Why it attracts. explicit invocation is the common case.
Why it fails. automatic discovery by description is a core feature.
When it would be right. only for skills that set disable-model-invocation: true.
Proposal. auto-invocation is triggered by the skill's filename.
Why it attracts. filenames often encode purpose.
Why it fails. the description field, not the filename, drives matching.
When it would be right. never.
- A mutation asks what enables auto-invocation - a good
description. Another asks how to prevent it -disable-model-invocation: true. Stable.
Paths-scoped skills auto-load against matching files
A skill can declare path scope via a paths frontmatter field (referenced in the tested material as a way to auto-load when working on matching files). When the user edits or operates on a file that matches the declared path pattern, the skill is loaded automatically. This is a file-driven variant of intent matching distinct from the description match.
Some skills are relevant because of which file is open, not because of the wording of the request. A paths declaration lets the system attach the skill to the file context, so the expertise appears exactly when the matching file is in play.
Boundary. (Uncertain official syntax: the tested material references a paths frontmatter field for path-scoped auto-loading, but the exact field name and matching semantics should be confirmed against current docs; flag before publishing authoritative guidance.) The nearby opposite case is a skill that should load by intent wording only - there a description match suffices and no paths is needed.
Recurring specifics. The recurring field reference is paths. The recurring trigger is "when you are working on matching files." The contrast is intent-match (description) versus file-match (paths).
Proposal. path-scoped loading is configured in CLAUDE.md.
Why it attracts. CLAUDE.md is the always-loaded layer.
Why it fails. path-scoped skills use their own frontmatter, not CLAUDE.md.
When it would be right. for always-on path conventions, .claude/rules/ is the better fit (Rule 18).
Proposal. path matching uses the skill filename.
Why it attracts. filename convention.
Why it fails. a dedicated paths field governs it.
When it would be right. never.
- A mutation changes the file type pattern. Another asks intent versus path matching. Stable once the field is confirmed.
disable-model-invocation: true forces explicit invocation only
The disable-model-invocation frontmatter field, when set to true, removes the skill from automatic discovery. The agent will not auto-load it based on description or path matching; the user must invoke it explicitly by name. This is the lever for skills that should never run unless deliberately summoned.
Some skills are powerful or disruptive and should not be triggered by a loose intent match. Disabling model invocation guarantees the human stays in control of when the skill runs, which is essential for skills that modify state or run external commands.
Boundary. The nearby opposite case is a benign, frequently-relevant skill that should auto-load - there disable-model-invocation is omitted so discovery works. The boundary is risk and intent: automatic-helpful versus must-be-explicit.
Recurring specifics. The field disable-model-invocation and the value true recur. The phrase "require explicit user invocation" recurs. This is listed among the live frontmatter fields beyond the three exam-tested ones.
Proposal. use hidden: true to prevent auto-invocation.
Why it attracts. hidden sounds like invisible.
Why it fails. hidden: true is not the documented mechanism for this; it is a fabricated option in the tested material.
When it would be right. never; the real field is disable-model-invocation.
Proposal. use auto_run: false to stop automatic runs.
Why it attracts. the name fits.
Why it fails. auto_run is a fabricated frontmatter key, not a real field.
When it would be right. never.
- A mutation asks which flag disables auto-loading -
disable-model-invocation. Another pairs it with explicit-only invocation. Stable.
Slash command bodies accept $ARGUMENTS placeholders for runtime parameters
Inside the body of a slash command file, the token $ARGUMENTS is replaced at invocation time with whatever the user typed after the command name. This lets a single command file accept variable input - a target file, a diff, a version string - without separate definitions per value. The placeholder is resolved by the command runner before the instructions are passed to the agent.
# .claude/commands/review-code.md
Review the following code for: security vulnerabilities, performance issues, and
adherence to our coding standards. Focus on: $ARGUMENTSCommands are reusable prompts; hardcoding inputs would defeat reuse. The $ARGUMENTS token is the minimal parameterization mechanism that keeps a command file static while letting each invocation supply its own data. It is the command-form equivalent of an argument-hint prompt, but inline rather than a picker label.
Boundary. The nearby opposite case is a command that needs no runtime input; there $ARGUMENTS is simply absent. The boundary is whether the command's behavior depends on user-supplied text. A command that ignores extra text still works; $ARGUMENTS only matters when the body references it.
Recurring specifics. The token $ARGUMENTS is the tested identifier. The recurring example is a /review-code command whose body says "Review the following: $ARGUMENTS." The command file is .claude/commands/review-code.md.
Proposal. parameters are passed via frontmatter argument-hint at runtime.
Why it attracts. the hint field relates to arguments.
Why it fails. argument-hint only prompts and labels; the runtime value flows through $ARGUMENTS in the body.
When it would be right. when the need is a prompt label, not value substitution.
Proposal. the command body reads arguments from a paths field.
Why it attracts. fields carry config.
Why it fails. paths scopes loading, not runtime values.
When it would be right. never for parameter passing.
- A mutation changes the parameter (diff, file pattern, version). Another asks whether the command needs separate files per value - no,
$ARGUMENTShandles it.
The slash command filename, minus extension, becomes the command name
For the flat command form, the file's base name determines the slash command that appears in the picker. .claude/commands/deploy.md yields /deploy; .claude/commands/review-code.md yields /review-code. The extension is stripped and the remainder, including hyphens, becomes the command name. There is no separate registration step that maps file to command.
Naming-by-filename removes a configuration burden: the file path is the command definition. The loader simply enumerates files in the commands directory and registers each base name as a command. This is why moving or renaming the file changes (or breaks) the command.
Boundary. The nearby opposite case is the skill form, where the directory name (not a file name) is the command: .claude/skills/deploy/ yields /deploy. The boundary is which path you are in: flat file name in commands/, directory name in skills/.
Recurring specifics. The recurring examples are deploy.md to /deploy and create-migration.md to /create-migration. The phrase "the filename becomes the command name" recurs. The extension is always dropped.
Proposal. the command name comes from a name field in frontmatter.
Why it attracts. explicit naming is common.
Why it fails. the file base name is the source of truth; no name field is required.
When it would be right. never for the commands form.
Proposal. renaming requires also editing CLAUDE.md to register the new name.
Why it attracts. registration feels needed.
Why it fails. no registration step exists.
When it would be right. never.
- A mutation changes the filename (
run-lint.md,generate-tests.md). Another asks about the extension's effect - it is stripped. Stable.
Name-collision precedence - a skill beats a same-named command; the canonical path wins
When two definitions share a command name - one as a flat file in .claude/commands/ and one as a directory in .claude/skills/ - the skills form takes precedence and becomes the active /command. The directory form is preferred because it is the canonical, feature-complete definition. This precedence is one of the three extra capabilities the skills path provides.
Resolving ambiguity in favor of the richer definition avoids splitting behavior across two sources. Since the skills form can carry supporting files, discovery, and isolation that the commands form cannot, letting it win keeps the feature-complete version authoritative.
Boundary. The nearby opposite case is two definitions both in the same path (both commands/ or both skills/) - there the resolution is by scope (user versus project) rather than by form, and the tested material is contradictory about which wins (Rule 25). The boundary is whether the collision is across paths (skill wins) or within a path (scope precedence is uncertain).
Recurring specifics. The recurring statement is "the skill wins" on name collision. The two locations .claude/commands/ and .claude/skills/ are the collision pair. The phrase "precedence when a skill and a command share the same name" recurs.
Proposal. the command defined later overwrites the skill.
Why it attracts. last-writer-wins intuition.
Why it fails. resolution is by form, not timestamp.
When it would be right. never for a cross-path collision.
Proposal. both definitions run, concatenated.
Why it attracts. merge feels safe.
Why it fails. only one command is registered.
When it would be right. never.
- A mutation swaps which path holds which definition; the skill still wins. Another asks what the commands alias loses on collision - precedence. Stable.
User-scope versus project-scope precedence for same-named skills is ambiguous in the tested material
When the same skill name exists in both user scope (~/.claude/skills/) and project scope (.claude/skills/), the tested material contradicts itself on which wins. One line of evidence states a personal same-named skill "takes precedence only in their environment" and overrides the project version. Another line states that same-named skills across scopes "create ambiguity and unpredictability" and recommends a distinct personal name to avoid any collision.
The contradiction likely reflects different versions of the guidance or different assumptions about whether user scope shadows project scope the way user-level CLAUDE.md can shadow project-level CLAUDE.md. Because the tested material does not reconcile them, this must be flagged as an open question rather than asserted.
Boundary. (Flag as CONTRADICTION: one source says user-scope same-named skill wins locally; another says aliasing creates ambiguity and a distinct name is required. Both positions appear in the tested material with equal confidence.) The safe, defensible recommendation that satisfies both readings is to give the personal variant a distinct name, which avoids the collision entirely regardless of which precedence rule is correct.
Recurring specifics. The recurring example is a personal /commit or /my-commit variant of a shared /commit skill. The cited mechanism "user scope winning" appears in one source; "ambiguity and unpredictability" appears in another. The distinct-name recommendation (/commit-mine, /my-commit) appears as the safe answer.
Proposal. add override: true to the personal skill so it supersedes the project version.
Why it attracts. override is intuitive.
Why it fails. override: true is a fabricated frontmatter key; no such mechanism exists.
When it would be right. never.
Proposal. add per-user conditional logic to the project skill's frontmatter.
Why it attracts. conditional config feels possible.
Why it fails. frontmatter is not a scripting layer for user branching.
When it would be right. never.
- A mutation changes the skill (
/commit,/generate-tests,/analyse). The contradiction persists across all of them. Record as open.
Personal customization is done in user scope with a distinct name to avoid clobbering teammates
To customize a shared team skill for personal use without affecting teammates, create the variant in user scope (~/.claude/skills/) under a distinct name, such as /commit-mine mirroring a shared /commit. The distinct name guarantees the two definitions never collide, so the shared skill remains untouched for everyone else and the personal one is invisible to teammates.
User scope is not version-controlled, so the personal variant never reaches the repository. A distinct name removes any precedence ambiguity (Rule 25), making the behavior deterministic: type /commit for the team standard, /commit-mine for the personal tweak. This is the lowest-risk path and the one repeatedly offered as the safe answer.
Boundary. The nearby opposite case is editing the shared skill directly in the project - that changes what every teammate experiences and risks committing personal preferences. The boundary is "affect others versus affect only self," and the distinct-name pattern is the clean separator.
Recurring specifics. The recurring names are /commit-mine and /my-commit. The recurring location is ~/.claude/skills/. The phrase "leaving the team's shared skill untouched" recurs.
Proposal. edit the shared skill in .claude/skills/ and revert before committing.
Why it attracts. temporary change feels safe.
Why it fails. it risks an accidental commit and affects teammates during the edit.
When it would be right. never as a recommended pattern.
Proposal. duplicate the skill into .claude/commands/ under a new name.
Why it attracts. commands folder is familiar.
Why it fails. it still mixes scopes oddly and the skills path is canonical; user scope with a distinct name is cleaner.
When it would be right. only if the team standard were already in commands form.
- A mutation changes the base skill (
/generate-tests,/analyse,/codebase-explorer). Another asks whether the personal variant is shared - no, user scope is personal. Stable.
Isolation frontmatter combines context: fork with allowed-tools for read-only analysis skills
For a verbose, read-only analysis skill, the recommended frontmatter combines two fields: context: fork to keep the noisy output out of the main conversation, and allowed-tools restricted to Read, Grep, and Glob so the skill cannot modify files or run shell commands. Together they make the skill both clean (isolated output) and safe (read-only capability). This combination is the canonical answer to "a skill that analyses the codebase and must not write."
---
description: "Scan the codebase for vulnerability patterns and report findings"
context: fork
allowed-tools:
- Read
- Grep
- Glob
argument-hint: "Provide the directory or module to scan (e.g., src/auth)"
---Verbose output and write capability are independent risks. context: fork addresses the context-budget risk; allowed-tools addresses the capability risk. Applying both satisfies scenarios that demand availability to every cloner (project scope, Rule 6), isolation (fork), and safety (read-only tools). The combination is greater than either alone.
Boundary. The nearby opposite case is a skill that must write files or run shell - there the allowed-tools list is extended to include those tools, and context: fork may still apply if output is verbose. The boundary is the required capability set: read-only analysis uses the restrictive list; generative workflows widen it.
Recurring specifics. The recurring field pair is context: fork plus allowed-tools: [Read, Grep, Glob]. The recurring scenario is a security-audit or codebase-analysis skill available to every developer, producing extensive output, modifying nothing. The location is project-scoped .claude/skills/.
Proposal. use context: fork alone, ignoring tools.
Why it attracts. isolation is the loudest symptom.
Why it fails. it does not enforce read-only; the skill could still write.
When it would be right. when safety is not a requirement, only context cleanliness.
Proposal. rely on a prompt instruction to avoid shell.
Why it attracts. self-restraint.
Why it fails. instructions do not remove capability.
When it would be right. never for an enforced boundary.
- A mutation changes the tool set or the verbosity, but the fork-plus-tools pattern persists. Another offers
commands/placement - acceptable but less featured thanskills/. Stable.
Command-file frontmatter is honored in both paths (description, allowed-tools, argument-hint)
The three tested frontmatter fields - description, allowed-tools, and argument-hint - work in both the flat command form (.claude/commands/name.md) and the skills form (.claude/skills/name/SKILL.md). A command file can carry the same YAML frontmatter at its top as a skill would. The skills path is canonical and adds discovery and supporting files, but frontmatter behavior is shared.
---
description: "Generate a changelog entry for the given version"
allowed-tools:
- Bash
argument-hint: "<version> (e.g., v1.4.0)"
---
Generate the changelog for $ARGUMENTS using the git history since the last tag.Because the two paths resolve into one command registry, the metadata that governs a command's behavior must be interpreted identically regardless of which folder it came from. Frontmatter is parsed the same way, so the fields mean the same thing in both forms.
Boundary. The nearby opposite case is the extra skills-only capabilities (supporting files, auto-discovery, precedence) that a command file cannot have even with frontmatter. Frontmatter parity does not grant those. The boundary is metadata (shared) versus structure-dependent features (skills only).
Recurring specifics. The recurring fields are description, allowed-tools, argument-hint. The recurring clarification is "both paths support the same YAML frontmatter." The command form is .claude/commands/name.md with frontmatter block at the top.
Proposal. frontmatter only works in SKILL.md, not in .claude/commands/.
Why it attracts. skills are the "rich" form.
Why it fails. command files also support frontmatter.
When it would be right. never; only the structural extras are skills-only.
Proposal. command files must use JSON frontmatter, skills use YAML.
Why it attracts. format differences are common.
Why it fails. both use YAML frontmatter.
When it would be right. never.
- A mutation changes the field being tested (
descriptionversusargument-hint). Another asks whether.claude/commands/supportsallowed-tools- yes. Stable.
context: fork is not a behavioral gate and does not keep CLAUDE.md instructions from applying
A recurring misconception is that context: fork can gate behavior - that wrapping a procedure in a forked skill prevents unrelated instructions from applying. It cannot. context: fork only relocates the skill's output to an isolated sub-agent context; it does not suppress CLAUDE.md content, which is loaded for every session regardless of forking. A translation procedure placed in CLAUDE.md with context: fork still loads and can still influence React maintenance work.
CLAUDE.md is part of the base session context, applied before any skill runs. Forking happens at skill execution time and affects only where that skill's output goes. The two layers are independent: forking never removes base context. To prevent accidental behavior, the procedure must not be in the always-loaded layer at all - it must be a skill invoked on demand (Rule 16).
Boundary. The nearby opposite case is a procedure that should genuinely be always-on - there CLAUDE.md is correct and forking is irrelevant. The boundary is "always-on versus on-demand," and forking is a tool for the latter, not a gate for the former.
Recurring specifics. The recurring wrong answer is "put translation instructions in CLAUDE.md with context: fork to isolate them." The recurring right answer is "put them in a skill so they are only invoked on demand." The phrase "conditional markdown blocks are formatting, not execution gates" recurs for the CLAUDE.md variant.
Proposal. context: fork in CLAUDE.md keeps the instructions from applying during React work.
Why it attracts. fork sounds like isolation of behavior.
Why it fails. CLAUDE.md loads regardless; fork only moves skill output.
When it would be right. never.
Proposal. a conditional markdown block in CLAUDE.md gates execution.
Why it attracts. conditionals feel like logic.
Why it fails. markdown blocks are formatting, not execution control.
When it would be right. never.
- A mutation swaps the languages (React to Vue, or any pair). Another removes forking and tests raw
CLAUDE.mdplacement - still wrong for on-demand. Stable.
Fabricated frontmatter and config surfaces are recurring distractors
A stable class of wrong answers invents configuration keys or surfaces that do not exist in the Skills system. These include override: true (to make a personal skill supersede a project one), auto_run: false and hidden: true (to disable auto-invocation), a commands array in .claude/config.json, a commands entry in settings.json, and registering commands inside CLAUDE.md. Each sounds plausible by analogy to other systems but maps to no real mechanism.
Exam distractors exploit intuitive naming: "override," "auto_run," "hidden," "config array" all exist in some tool somewhere, so they feel right. The real system relies on positional scoping (paths), real frontmatter fields (description, allowed-tools, argument-hint, context: fork, disable-model-invocation, disallowed-tools, paths), and the SKILL.md entrypoint convention. Anything outside that set is suspect.
Boundary. The nearby opposite case is a real field doing a vaguely similar job - for example disable-model-invocation: true is the real way to force explicit invocation, not auto_run: false or hidden: true. The boundary is the documented field set versus invented keys. When a distractor names a key not in the real set, it is wrong even if the intent is correct.
Recurring specifics. The fabricated keys that recur are override: true, auto_run: false, hidden: true, and the config surfaces .claude/config.jsoncommands array and settings.jsoncommands. The real alternatives are scope (paths), disable-model-invocation, and the documented frontmatter fields.
Proposal. set override: true on a personal skill.
Why it attracts. override semantics are familiar.
Why it fails. no such key exists.
When it would be right. never; use a distinct name in user scope instead.
Proposal. register commands in settings.json under commands.
Why it attracts. settings files configure behavior.
Why it fails. there is no command registration surface there.
When it would be right. never; placement under .claude/ is the mechanism.
Proposal. add the command to a commands array in .claude/config.json.
Why it attracts. config arrays are common.
Why it fails. no such surface exists.
When it would be right. never.
- A mutation changes which intent is faked (precedence, auto-run, registration). The rule holds: if the key is not in the documented set, it is a distractor.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| .claude/commands/name.md | .claude/skills/name/SKILL.md | Both create /name and work the same at runtime, skills path is canonical because it supports supporting files and auto discovery and wins when names collide. |
| Skills on demand workflows | CLAUDE.md always on standards | Skills load only when invoked directly or when description or paths matches intent. CLAUDE.md loads on every session with no invocation step. Task procedures belong in skills, universal conventions belong in memory. |
| context fork skill | Inline skill | Forked runs in an isolated subagent whose output does not enter the main conversation except as a summary. Inline runs in the main conversation. Verbose exploration belongs in forked skills. |
| disable-model-invocation true | allowed-tools only | The first removes the skill from Claude auto invocation entirely. The second controls which tools run without prompting when it does run. One governs whether it fires, the other governs tool surface while it runs. |
| Project .claude/skills | User ~/.claude/skills | Project is shared via git so every clone gets the command. User is personal and not shared. Team commands belong in project, personal workflows belong in user. |
| Built in fixed command | Bundled skill command | Fixed runs hardcoded CLI logic such as /clear. Bundled skill such as /code-review gives Claude instructions and lets it orchestrate with tools, so output is variable and tool dependent, and behavior in -p mode differs. |
Traps
The tempting answer. Create review.md directly inside .claude/skills and expect slash command /review to appear.
Why it fails. Skills require a directory containing SKILL.md, not a loose file. A loose file in .claude/skills is not discovered and no command is created, while the flat file path that does create one is .claude/commands/name.md.
What is correct. Create .claude/skills/review/SKILL.md or .claude/commands/review.md, both of which create /review, and keep the flat versus directory rule straight.
The tempting answer. Create a team shared command in ~/.claude/commands or ~/.claude/skills because it works on the author's machine.
Why it fails. User scope is personal and not version controlled, so teammates never see the command on clone. The sharing boundary mirrors CLAUDE.md, project scope via git is required for team sharing.
What is correct. Place team commands in .claude/skills or .claude/commands at the repo root so every clone inherits them.
The tempting answer. Put team wide API naming that should apply to every generation in a skill because skills are flexible.
Why it fails. Skills load on demand, so the convention applies only when the skill is invoked. Universal standards that must apply to every session belong in CLAUDE.md or path scoped rules.
What is correct. Put always on conventions in CLAUDE.md or .claude/rules with paths, and reserve skills for occasional workflows that a developer runs explicitly or that auto trigger via a precise description.
The tempting answer. Omit context fork for codebase analysis or brainstorming because the logic is straightforward without isolation.
Why it fails. Without isolation the verbose listings and excerpts fill the main context window and degrade subsequent quality. Fork keeps the main conversation clean and is the exam answer for that symptom.
What is correct. Add context fork in frontmatter for any skill whose output is noisy or exploratory, and return only a summary to the main session.
The tempting answer. Treat allowed-tools as a strict restrictor that removes all unlisted tools from the skill.
Why it fails. In current Claude Code allowed-tools pre approves listed tools for promptless use while the skill is active, other tools remain callable subject to baseline permissions. The true boundary is disallowed-tools or deny rules in settings.json.
What is correct. Use disallowed-tools or settings.json deny rules when a true removal is needed, and use allowed-tools to grant promptless access for the intended read or write tools.
The tempting answer. Invoke session management commands such as /clear or /compact headlessly via claude -p for CI orchestration.
Why it fails. Most session management commands are interactive only and do not apply headlessly. User invoked skills and custom commands do work in -p by including /skill-name in the prompt.
What is correct. In CI with -p invoke custom skills via slash name in the prompt and avoid interactive only commands, using print mode flags for output control instead.
Path-Specific Rules
Load conventions only for files whose path matches a glob so file type rules cover many directories with one file and minimal token cost.
What you need to know
Path specific rules are the mechanism for applying conventions conditionally based on which files are being edited, solving the gap that neither root CLAUDE.md nor directory level CLAUDE.md handles well. Root loads for every session regardless of which files are touched, so Terraform conventions burn tokens while editing React components. Directory level applies only to a single directory, so covering test files co located with source across 50 plus directories would require one CLAUDE.md per test directory and inevitable drift. Path scoped rules solve the cross directory, single file type case with one file and one glob.
Rule files live in .claude/rules/ and each carries YAML frontmatter with a paths field whose value is an array of glob patterns using star, star star, question, and bracket semantics matched against the project root. With no frontmatter the file loads for every session, which is useful for universal standards but wasteful when the rule only matters for one file type. With paths such as star star slash star dot test dot ts the file loads only when Claude reads a matching file, staying invisible until relevant and staying cheap until triggered. That conditional cost is the token efficiency the exam tests directly.
The mechanism earns its keep in the canonical scenario of test files beside source files across dozens of directories. A project with src/components/Button.test.tsx next to Button.tsx and similar pairs in api, utils, and pages needs one test convention file, not 50 copies. A path scoped rule with paths star star slash star dot test dot tsx and siblings applies the same naming, assertion, and mocking standards everywhere automatically. The alternatives fail visibly: directory level would need manual placement in every new test directory, root would load test rules while writing an API handler.
Glob breadth is what makes this work. star star matches any number of segments including zero, star matches a single segment, and the match root is the project root, so star star slash star dot test dot tsx matches every test file anywhere in the repo and star star slash routes slash star star slash star dot ts matches every routes file regardless of how many packages expose routes. The closest companion mechanism for the inverse case is claudeMdExcludes in settings.json, which skips discovery of CLAUDE.md files whose file path matches a glob such as star star slash experimental slash star star when another team's rules keep surfacing in a noisy monorepo. There is no native exclusion list inside paths frontmatter itself.
Loading is incremental and composes with the walk up and lazy mechanisms. At launch the walk up chain contributes any CLAUDE.md above the working directory. When Claude reads a file, the subdirectory CLAUDE.md for that file's parent loads if not already present and any path scoped rule whose paths glob matches loads, and once loaded the rule stays in context for the remainder of the session. A unified root file with all convention sets might weigh 800 tokens, while a session editing only Terraform might load universal standards plus the Terraform rule for about 500 tokens, so the savings compound across turns and reduce lost in the middle noise.
Frontmatter shape and match semantics
Each rule in .claude/rules/name.md begins with YAML frontmatter, the only tested key is paths which takes an array of globs. Standard glob semantics apply, star for a single segment, star star for any depth including zero, question for a single character, bracket expressions as in any glob engine. The matching root is the project root, so star star slash star dot test dot tsx matches every test file in any subtree without enumerating directories.
A file with no frontmatter or with an empty paths array is treated as loaded for every session, which is the form for universally applicable standards. That form is intentionally wasteful if the content is file type specific, so the exam marks the path scoped form as the cost correct alternative when the question notes that conventions should not consume tokens while doing unrelated work.
Why directory and root answers fail the canonical case
Directory level CLAUDE.md is scoped to a single directory subtree, so bill a cross directory convention such as test conventions across many source directories with that mechanism and the correct count is one file per directory containing a test. The maintenance burden is to create the file in each current test directory and to add it in every new one, which drifts as copies fall behind, and the exam presents this as the maintenance failure that path scoped rules eliminate.
Root CLAUDE.md is unconditional, it loads for every session even while editing a file the conventions do not apply to. That cost is measurable in context budget and in lost in the middle attention where irrelevant guidance dilutes focus on the work at hand. The exam directly asks which mechanism keeps Terraform conventions from taxing React work and tests path scoped rules versus root as the efficiency choice.
Lazy composition with CLAUDE.md
The lazy trigger is the read operation. When Claude reads a file outside a rule's patterns, that rule does not load. When the read matches, the rule loads and then stays for the session. This is the same trigger as subdirectory CLAUDE.md, which is what keeps per package cost low in monorepos where a session in packages/web never pays for packages/payments until touched.
The incremental view explains the /context observations the exam tests. Start in the root and open a dot test dot ts file under src/utils, the walk up chain contributes universal standards while testing.md matches and loads and api-conventions.md and terraform.md do not. Open a Terraform module under terraform/modules/vpc and terraform.md matches and loads, while earlier test rules persist but no new unrelated rules contribute.
Composition with @path imports and exclusion
Path scoped rules compose with @path imports. A shared file with universal standards can be referenced from multiple path scoped rules via @, or a rule can import shared standards while keeping its own conditional body focused on the matching file type. This avoids duplicating the unconditional part across many rules while keeping the conditional part precise.
The file path glob for claudeMdExcludes is distinct from the directory glob for paths. Excludes match against CLAUDE.md file paths to skip, so a pattern excluding experimental removes every CLAUDE.md under that subtree. The distinction matters because an exclude target such as irrelevant team's CLAUDE.md is a file to skip, not a directory of source to match.
Authoritative mechanism reference
The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.
Mechanism reference: The conditional loading primitive
Path-specific rules are ordinary markdown files inside .claude/rules/ whose YAML frontmatter contains a paths array. The presence of that array is the entire gating mechanism. When the array is present, the rule is conditional. When it is absent, the rule is unconditional and loads at launch with the same priority as .claude/CLAUDE.md. The frontmatter is delimited by opening and closing --- lines, and the body below the frontmatter carries the conventions as markdown. The documentation shows the exact form, including list syntax with a dash per entry.
The documented trigger is a file read. A path-scoped rule triggers when Claude reads a file whose repository-relative path matches at least one pattern in the paths list. As of version 2.1.198, the match also succeeds when the file is reached through a symlinked path to the project directory, such as a symlinked checkout. The rule does not trigger on every tool use, it triggers on reads against matching paths, which in an editing session means the conventions become visible for the files being edited that satisfy the glob.
Three related behaviors clarify what does not happen. First, subdirectory CLAUDE.md files are also lazy: they are included when Claude reads files in that subdirectory, not at launch. Second, imports written as @path/to/file inside any CLAUDE.md are expanded and loaded at launch, not lazily, so they do not provide conditional loading. Third, rules without paths are discovered recursively even in nested .claude/rules/ subdirectories, and before version 2.1.211 such on-demand rules loaded even when project was excluded from settingSources, after which they respect that exclusion.
The file-system layout for rules is explicit. Place markdown files in .claude/rules/ with descriptive names such as testing.md or api-design.md, and organize into subdirectories like frontend/ or backend/ when that mirrors the codebase. The example tree in the documentation shows your-project/.claude/rules/ containing code-style.md, testing.md, and security.md alongside a main CLAUDE.md. Symlinks inside .claude/rules/ are resolved normally, circular symlinks are handled gracefully, and a shared directory can be linked in with ln -s ~/shared-claude-rules .claude/rules/shared.
Mechanism reference: Which frontmatter key names are documented
The authoritative name is paths. Every code sample in the current documentation uses paths as the YAML key, introduced with the sentence that rules can be scoped using YAML frontmatter with the paths field. The same page describes rules without paths frontmatter as unconditional, establishing paths as the sole field that creates the conditional behavior. The lesson claude-code-mdc-config reinforces this by stating explicitly that there is no separate .mdc file format and that the composition mechanisms that exist are CLAUDE.md, @path imports, and claudeMdExcludes, not a glob-matched config system with alternative frontmatter.
The forensics peer file flags uncertainty between paths and a competing name globs, and between paths and alwaysApply paired with globs and description. That competing vocabulary belongs to a different editor's rules format that the lesson calls out as a distractor. If an option on the exam describes a .mdc file with alwaysApply, description, and globs for Claude Code, the lesson warns that this is borrowed from another tool's format and should be treated as incorrect for Claude Code. The configuration lesson configuration does not define any globs or alwaysApply keys for rules, it defines @path imports and directory-level CLAUDE.md as the composition primitives.
Practical guidance for authoring real files follows directly. Use paths and only paths when writing a file under .claude/rules/ that must load conditionally in Claude Code. If you encounter globs in existing files carried over from another tool's migration via /import, verify and rename to paths against the documentation before relying on conditional loading, and mark any file that still uses globs as unverified until checked. The alwaysApply boolean and the description string that appear in the alternative style do not appear in the Claude Code memory documentation at all; the closest documented analogue is the absence versus presence of paths itself, where absence means always loaded and presence means gated. The alternative style's alwaysApply: true corresponds to omitting paths, and alwaysApply: false with a path list corresponds to including paths. The description field in that style is described as an intent trigger for model-side selection, which is probabilistic, not structural, and is the same class as prose gating that the documentation contrasts with structural paths matching.
Mechanism reference: What happens when the paths key is absent
A file in .claude/rules/ with no paths frontmatter, or with frontmatter that omits paths, is unconditional. The documentation states two equivalent formulations. First, rules without paths frontmatter are loaded at launch with the same priority as .claude/CLAUDE.md. Second, rules without a paths field are loaded unconditionally and apply to all files. Either way the effect is the same: the rule body enters context for every session, no file read is required to trigger it, and the location .claude/rules/ alone does not make it conditional.
This matters for incremental adoption. A team that begins with shared conventions in .claude/rules/security.md and forgets to add a paths gate has created an always-loaded file even though it lives under rules/. The fix is to add a frontmatter block with the intended scope or to move the content into a universal CLAUDE.md if the intent was always-loaded. Conversely, a rule that intentionally uses paths: ["/"] is effectively always-loaded by pattern rather than by omission, because / matches every file, but the documentation still treats it as a gated rule that happens to match everything, not as the idiomatic way to express universal intent. The idiomatic location for universal standards remains project-level CLAUDE.md.
The same absence rule applies to ~/.claude/rules/ at user scope. Personal rules in ~/.claude/rules/ apply to every project on the machine, and when they carry no paths they load before project rules as part of the always-loaded set.
Mechanism reference: Glob pattern grammar and documented examples
The paths field holds glob patterns matched against file paths, not file contents. The documentation provides a pattern table and several inline examples that together define the grammar candidates should recognize.
Documented patterns and their meanings:
*/.tsmatches all TypeScript files in any directory.src/*/matches all files under thesrc/directory at any depth.*.mdmatches markdown files in the project root.src/components/*.tsxmatches React components in a specific directory without recursion into its subdirectories.
The difference between and is structural. A single matches within one path segment and does not cross a / separator, while matches zero or more segments across separators. That is why */.test.tsx reaches every test file in the reference page's tree (src/components/Button.test.tsx, src/api/auth.test.ts, src/utils/format.test.ts, src/pages/dashboard/Dashboard.test.tsx) from one file, whereas /.test.tsx would only reach one level of nesting.
Brace expansion is documented as a multiplier. A pattern such as src//.{ts,tsx} expands to two concrete patterns, src//.ts and src/*/.tsx, and {a,b}/{c,d}/*.{ts,tsx} expands to eight. To keep expansion bounded, the whole paths list shares a budget of 1,000 expanded patterns and 4 MiB, and patterns without braces do not count against that budget. Before version 2.1.217 a paths value with many brace groups stalled or crashed the CLI at startup, so the budget is not merely advisory.
Bracket expressions are also specified. Glob syntax treats [ as the start of a bracket expression such as [abc]. A pattern with a [ that cannot be read as a bracket expression, such as photos [2024/, is invalid: it matches nothing while the rule's other patterns keep working. To match a literal [, escape it as photos \[2024/. Before version 2.1.207 a single invalid pattern made the Read tool fail for every file the rule was evaluated against, instead of matching nothing.
The pattern vocabulary that the reference page uses is fully covered by these documented forms. Extension globs such as /.tf, /.py, /.spec.ts, and /.controller.ts are instances of /.ts. Subtree globs such as terraform// and infrastructure// are instances of src//. The build exercise patterns /.test.ts, /.test.tsx, /.spec.ts, src/api//, /routes//, terraform//, and /*.tf each map directly to a documented row, so every recommendation in the exercise is backed by an official pattern class.
Mechanism reference: Whether negative patterns are supported
The documentation does not describe a negation prefix such as !/__generated__/ or !vendor/** as part of the paths grammar. The documented features are positive globs, brace expansion, bracket handling, and the 1,000-pattern budget. Negation with ! is therefore marked not independently confirmed for Claude Code's paths field.
The forensics file includes negative globs as a recurring pattern from community material, framed as moving exclusions carved out of a type-wide rule. That framing is accurate for the scenario it describes, generated and vendored directories that should not be governed by authored-file conventions, but the documented mechanism for excluding specific instructions from context is different. For instructions that should not apply to a subtree, the documentation provides claudeMdExcludes in settings.json, which skips specific files by path or glob matched against absolute paths, with arrays merging across user, project, local, and managed layers, while managed policy files cannot be excluded. For path-scoped rules, the positive-form remedy is to narrow the inclusion: replace /.ts with a more specific positive scope such as src//.{ts,tsx} that naturally avoids the generated tree, or split authored guidance into a separate always-loaded CLAUDE.md imported only where needed. Candidates should not assume ! works in paths without verifying against the current documentation, and on the exam should prefer the documented exclusion primitive claudeMdExcludes or a tighter positive glob over an unverified negation syntax.
Mechanism reference: How overlapping patterns compose
When two rule files have overlapping paths, a file whose path matches both loads both bodies. Each file is evaluated independently against its own paths list, and there is no documented merging that collapses distinct rule files that both match into a single load. The documentation instead warns that if two rules contradict each other, Claude may pick one arbitrarily, which implies that both are present and their guidance competes for attention.
The forensics file notes the same property: a file at src/api/auth.test.ts matches both /.test.ts and src/api//, so both rule texts are visible for that file and the token cost is the sum of the two bodies. This additive cost is the concrete implication of overlapping scopes. The documentation does not define a precedence order that makes one file override another in the way settings.json layers override scalars. The closest documented ordering is for CLAUDE.md concatenation, where content is ordered from the filesystem root down to the working directory and the closest file is read last, giving it the strongest last word without discarding anything. For rules, the user-level versus project distinction is documented: user-level rules in ~/.claude/rules/ are loaded before project rules, giving project rules higher priority. Beyond that tier, no rule-file precedence is specified, so overlapping rules from the same tier should be treated as co-visible with additive cost and potential contradiction not independently confirmed.
A single file with many patterns also has a composition rule. Each brace group multiplies expanded patterns, and the whole paths list shares one budget of 1,000 expanded patterns and 4 MiB. Patterns without braces do not count against the budget. Any pattern that would exceed the budget is used unexpanded and its literal braces match no files, so an over-budget brace pattern silently matches nothing rather than crashing after the version 2.1.217 fix.
Mechanism reference: Token-cost comparison against always-loaded memory
The token cost model follows directly from the loading rules. Every file loaded at launch consumes context for the entire session. The documentation advises keeping each CLAUDE.md under 200 lines because longer files consume more context and reduce adherence, and it notes that a CLAUDE.md over 4 MiB is skipped entirely. Imports written as @path/to/file are expanded and loaded at launch alongside the CLAUDE.md that references them, so splitting a large CLAUDE.md into imported modules improves maintainability but does not reduce tokens, the imported text is still in context for every session.
Path-scoped rules have a different cost shape. They only load when Claude works with matching files, reducing noise and saving context space for sessions that never touch those files. The documentation explicitly presents this as the remedy when instructions are growing large: use path-scoped rules so instructions load only for matching files rather than keeping everything in CLAUDE.md. The reference page mirrors this by stating that path-scoped rules stay invisible until relevant and that root CLAUDE.md loads for every session even when the conventions inside are file-type limited.
Three concrete comparisons illustrate the difference. First, infrastructure conventions written as paths: ["/.tf", "terraform//"] in .claude/rules/terraform.md consume zero tokens in a session that only edits React components under src/components/, because no read matches */.tf. Second, the same conventions placed in root CLAUDE.md consume tokens in that React-only session, because root CLAUDE.md is always present. Third, subdirectory CLAUDE.md files have an intermediate cost: they are not loaded at launch but are included when Claude reads a file in that subdirectory, so a package-specific rule in packages/api/CLAUDE.md costs nothing to a session that never touches packages/api/.
The forensics file adds a quantitative anecdote about a developer who never triggered a Terraform rule while spending the day in React, for a zero cost that aligns with the documented lazy model. No official figure for percentage savings across a codebase is documented, so this grounding does not present one as measured. The qualitative statement that path-scoped rules keep token budget focused on relevant conventions and that root-bloated files dilute attention is confirmed by the documentation's size guidance and its recommendation to scope by path when possible.
Ownership map
Path-specific rules sit at the intersection of four layers. The table below assigns which layer owns which guarantee and where failures surface when the wrong layer is used.
| Guarantee | Owner | What the owner controls | Failure mode when another layer is used |
|---|---|---|---|
| Whether a rule loads for this file | CLI runtime that evaluates paths globs against read paths | Pattern expansion, budget enforcement (1,000 patterns, 4 MiB), bracket validation, symlink-aware matching as of 2.1.198 | Prose headers in CLAUDE.md cannot prevent loading, the text is already in context and the gating is probabilistic |
| Whether a rule is available at all | File system and settingSources selection | Discovery of .md files under .claude/rules/ recursively, respecting project exclusion after 2.1.211, and honoring symlinks | Placing a rule outside .claude/rules/ or excluding project without realizing it hides the rule |
| Whether instructions are followed | Model interpreting context | Attention over the concatenated instruction set, with last-word ordering for CLAUDE.md hierarchy but no override for conflicting rules | Contradictory rules cause arbitrary selection, not deterministic override |
| Whether behavior is enforced regardless of interpretation | Hooks and settings.json permission layers | PreToolUse and related hook events that can block a tool call outside model judgment | Writing a prohibition only in CLAUDE.md leaves it as guidance, not enforcement |
| Whether irrelevant instructions are present in context | Configuration composition (@path imports and claudeMdExcludes) | Imports load eagerly at launch, exclusions skip by glob against absolute paths, with managed policy unable to be excluded | Using @path to simulate conditional loading keeps the imported file always present |
Three of these distinctions are tested most often. First, model versus CLI ownership of gating. The model can describe intent such as applying a section only when working in ios/, but the CLI evaluates paths globs structurally before the session, so only paths makes a section invisible to unrelated sessions. Second, application code versus instructions. Tool security lessons emphasize that input validation and secrets handling belong in code that runs, not in conventions that describe desired behavior, because conventions are not enforced by the client. Third, CLAUDE.md hierarchy versus rule scope. Directory-level CLAUDE.md files inside packages/api/ or ios/ are discovered by walking the tree and by lazy inclusion on read, while .claude/rules/ files are discovered by glob evaluation, so the two mechanisms have different cost shapes even though both can express directory affinity.
The SDK adds a further ownership nuance. When Claude Code is embedded via the SDK, the caller selects which setting sources to honor. By default the SDK loads no filesystem settings, so project CLAUDE.md, rules, and skills are ignored unless settingSources includes project. An SDK agent that appears to ignore .claude/rules/ is usually not evaluating globs incorrectly, it is not loading them at all because the source selection excluded the tier that contains them.
The managed tier sits above all of these. Managed policy CLAUDE.md at /Library/Application Support/ClaudeCode/CLAUDE.md on macOS or /etc/claude-code/CLAUDE.md on Linux and the claudeMd key inside managed-settings.json both load before user and project tiers and cannot be excluded. The same page documents that claudeMdExcludes arrays merge across layers but never affect managed files, which is how an organization guarantees that security baselines survive individual overrides.
Version and terminology currency
The current memory documentation reflects the .claude/rules/ system with paths as the frontmatter key, which the lesson set describes as the present mechanism for directory-scoped and path-scoped guidance. The lesson claude-code-mdc-config is explicit that there is no .mdc file format in Claude Code and that an exam option describing .mdc with YAML frontmatter and glob patterns for Claude Code is a distractor borrowed from another tool. The same lesson describes the real primitives as directory-scoped CLAUDE.md files concatenated in walk-up order, subdirectory lazy loading, @path imports with a maximum depth of four hops, and claudeMdExcludes for skipping irrelevant files.
The forensics file's reference to .mdc style fields alwaysApply, description, and globs aligns with that warning. Those field names are not documented on the Claude Code memory page at all. The exam guide may have described an older or analogous concept, and the lesson notes that the tooling previously incorporated Cursor rules from .cursor/rules/ into generated CLAUDE.md via /init. That import path through Cursor rules, in .cursor/rules/ or .cursorrules and the later addition of AGENTS.md, .devin/rules/, .windsurf/rules/, and .clinerules when CLAUDE_CODE_NEW_INIT=1 is set shows that Claude Code can ingest foreign formats, but the native authoring format after ingestion is CLAUDE.md and .claude/rules/ with paths, not .mdc.
Several version gates affect behavior at the boundary.
- Matching through symlinked project paths became reliable in version 2.1.198.
- Invalid bracket patterns such as
photos [2024/**changed from causing the Read tool to fail for every evaluated file to matching nothing while other patterns keep working in version 2.1.207. - On-demand rules including path-scoped rules and rules in nested
.claude/rules/directories began respecting theprojectexclusion insettingSourcesin version 2.1.211, before which they loaded even whenprojectwas excluded. - Brace-heavy
pathsvalues that previously stalled or crashed the CLI at startup became bounded by the 1,000-expansion and 4 MiB budget in version 2.1.217, after which over-budget patterns are used unexpanded and match nothing. - The interactive multi-phase
/initflow gated byCLAUDE_CODE_NEW_INIT=1and the one-time/importfor carrying over MCP servers and skills from other agents arrived in versions 2.1.213 and later.
Terminology continuity: the reference page uses paths as the field name for conditional loading, which matches the current documentation. Older community material that uses globs as the array name describes the same intent under a different tool's vocabulary and should be translated to paths when authoring for Claude Code, with the translation verified rather than assumed not independently confirmed.
Path semantics are repository-relative in the reference page's examples. The documentation's pattern table is consistent with that, showing /.ts for any directory and src// for a rooted subtree, without introducing absolute filesystem roots into the paths list. The exclusion side uses absolute-path matching: claudeMdExcludes patterns are matched against absolute file paths using glob syntax. Candidates should keep those two scopes distinct: paths globs match repository-relative paths for rule activation, while claudeMdExcludes globs match absolute paths for skipping already discovered instructions.
Official versus community divergence
The documentation and the forensics file agree on the core pattern but diverge in three places where community material introduces vocabulary or syntax that the documentation does not.
Divergence 1: field names. Documentation uses paths. Community material in the forensics file attests both paths and globs, with paths described as dominant and globs as a secondary legacy spelling, alongside an alternative style that pairs alwaysApply, description, and globs. The lesson set is direct on this point: a .mdc format with globs and alwaysApply for Claude Code is a distractor borrowed from another tool's rules format. Position: candidates should answer with paths for Claude Code. The mapping that globs means paths is plausible but unverified for this repository's CLI until checked against the current documentation, so mark any globs usage as not independently confirmed rather than assuming equivalence.
Divergence 2: negation syntax. Forensics includes negative globs such as !/__generated__/ and !vendor/** as moving exclusions carved out of a type-wide rule. The current documentation does not describe a ! prefix in paths, its documented exclusions are through claudeMdExcludes in settings.json and through tighter positive globs for rules. Position: on the exam, prefer the documented exclusion primitive claudeMdExcludes or a narrower positive paths set over ! negation. Treat any ! pattern in paths as not independently confirmed and verify by testing against a real repository before relying on it.
Divergence 3: token-saving arithmetic. Forensics records anecdotal bleed observations in sessions that rely on prose gating in oversized CLAUDE.md files, and the reference page describes the efficiency gain as substantial without attaching a figure. Community guides sometimes present percentage reductions as if they were product-measured, such as token savings claims for path-scoped versus always-loaded guidance. The documentation provides qualitative sizing guidance (target under 200 lines per file, skip over 4 MiB) and the architectural reason (conditional rules load only for matching files) but no official numeric saving factor. Position: cite the documented mechanism and the size thresholds, not community percentage arithmetic, and explicitly avoid presenting unofficial savings as measured official figures.
Two additional contrasts that are not contradictions but are common traps align cleanly. The note under rules states that skills load on demand instead of every session, which matches the reference page's distinction that path-scoped rules stay in context as background guidance for matching edits while skills are task workflows triggered by intent or explicit invocation. Similarly, the documentation's statement that imports are expanded at launch matches the reference page's warning that @import does not make loading conditional.
When documentation and community material conflict, documentation wins for answer selection, with the divergence noted as above so the candidate understands both what will be scored as correct and what the community source expected.
Beyond the task statement
The lessons surrounding path-specific rules cover adjacent directory-scoped and configuration concepts that the reference page omits but that shape every decision about where a convention should live.
Directory-scoped CLAUDE.md discovery and concatenation. Discovery walks up the directory tree from the working directory, checking each directory for CLAUDE.md and CLAUDE.local.md, and concatenates every discovered file into context rather than merging with override. Content is ordered from the filesystem root down to the working directory, so root content appears first and the closest file is read last. Subdirectory files load lazily when a file in that subdirectory is read. Why it matters for this task: paths versus directory-level placement is a cost decision, and lazy loading is the reason a packages/api/CLAUDE.md that never has its directory touched costs nothing. Lesson slug claude-code-mdc-config and lesson slug configuration are the canonical references.
@path import composition. Any CLAUDE.md can include @path/to/file anywhere in its body, resolved relative to the importing file, recursive up to four hops, and ignored inside code spans and fenced blocks. Imports load at launch alongside the importing file. Why it matters: imports solve duplication of always-loaded guidance without changing load behavior. A single canonical security baseline can be imported into each project's CLAUDE.md as the reference source rather than copied, but imported file-type guidance remains always loaded and therefore is not the remedy for token bloat caused by irrelevant conventions.
claudeMdExcludes. A settings.json array that skips specific CLAUDE.md files by path or glob matched against absolute paths, with arrays merging across user, project, local, and managed layers. Managed policy files cannot be excluded. Why it matters: it is the documented exclusion primitive when cross-team CLAUDE.md files surface in a monorepo. Candidates who try to exclude instructions with ! inside paths should use claudeMdExcludes or narrower positive paths instead.
CLAUDE.md hierarchy tiers. Managed policy, user at ~/.claude/CLAUDE.md, project at ./CLAUDE.md or ./.claude/CLAUDE.md, and local at ./CLAUDE.local.md, each with distinct sharing through version control. User-level ~/.claude/rules/ rules load before project rules, giving project rules higher priority. Why it matters: the same markdown text at user level and at project level has different reach, and team standards must live at project level to survive cloning.
Permission and hook separation. Tool permissions (allow, ask, deny) live in settings.json, not in CLAUDE.md, and hooks such as PreToolUse can block or rewrite a tool call outside model judgment. The settings hierarchy for settings.json overrides scalars layer by layer, from user through project and local to CLI arguments and managed policy on top. Why it matters: a prohibition that must hold regardless of interpretation needs a deny rule or a hook, not a line in a path-scoped rule that is merely context.
Multi-package monorepo layout. Per-package CLAUDE.md files such as packages/api/CLAUDE.md and packages/web/CLAUDE.md that load lazily when their package is touched, contrasted with the fabricated .mdc glob system that the lesson marks as a distractor. Why it matters: it explains why the reference page's recommended fix for 50 directories is one path-scoped rule under .claude/rules/ rather than one CLAUDE.md per directory, while also validating the narrow case where a single package directory truly owns a subtree and a directory-level file is the tighter fit.
Context management and session hygiene. Explicit guidance to use /clear between unrelated tasks, scope file loading to the task, keep CLAUDE.md clean, use CLAUDE.local.md for ephemeral notes, and break sessions at natural boundaries. Verification uses /context to list loaded memory files and /memory to browse and edit them. Why it matters: these are the observable diagnostics for the build exercise, where the candidate switches between a test file and an API handler and watches which rules appear in /context.
SDK source selection. The SDK requires settingSources to include project to see filesystem rules, otherwise .claude/rules/ is invisible to the session. Why it matters: a rule that is correct on disk can appear to be ignored when the session's source selection excludes its tier.
Worked production examples
Each example pairs a repository layout, a concrete goal, the reasoning chain that selects the mechanism, and the failure mode being avoided. Every code block below is a complete file as it would be committed, not a fragment.
Worked production examples: Example 1: Conditional rule file with a paths array for testing
Goal: enforce the same test conventions for every test file that is co-located beside its source across dozens of directories, without loading those conventions during backend-only or infrastructure work.
Repository layout before the change: src/components/Button.tsx with src/components/Button.test.tsx, src/api/auth.ts with src/api/auth.test.ts, src/utils/format.ts with src/utils/format.test.ts, and src/pages/dashboard/Dashboard.tsx with src/pages/dashboard/Dashboard.test.tsx. The team considered putting one CLAUDE.md in each directory to cover tests, which would require a file per directory and a copy per new feature folder.
Decision chain: the convention follows a file type, not a fixed subtree, so a directory-level CLAUDE.md per folder duplicates guidance and drifts. Root CLAUDE.md would make the conventions always loaded even for sessions that never touch tests. The documented remedy for instructions that only matter for part of the codebase is a path-scoped rule that loads only for matching files. The frontmatter key is paths.
Committed file:
---
paths:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.spec.ts"
- "**/*.spec.tsx"
---
# Test Conventions
- Use `describe` and `it` blocks with descriptive names that read as sentences.
- Each test file must have at least one happy path and one error case.
- Use factory functions for test data, not inline object literals.
- Mock external services at the module boundary, not individual functions.
- Assert behaviour, not implementation details.
- Ban snapshot tests for component contracts, prefer explicit expectations.Verification: edit src/components/Button.test.tsx and run /context. The output lists .claude/rules/testing.md under Memory files. Edit src/api/auth.ts and run /context again. The testing rule is absent. That absence is the token saving: the rule contributed nothing to the API-only session. The failure avoided is drift from 50 copies and token waste from an always-loaded root section with a prose header such as Apply the following only when working on test files, which remains in context and depends on probabilistic application rather than structural gating.
Edge handling: */.test.ts does not match src/pages/dashboard/Dashboard.ts even though that file may mention tests in its content, because matching is against the path string, not contents. A rule without paths would have loaded for the non-test file, confirming why the gate must be explicit.
Worked production examples: Example 2: Whole-tree extension globs for infrastructure
Goal: govern every Terraform file in a polyglot platform where the same extension appears under terraform/, infra/terraform/, modules/, and services/billing/infra/, with new infrastructure folders expected each quarter.
Repository layout: terraform/networking/vpc.tf, infra/terraform/environments/production/main.tf, modules/compute/main.tf, services/billing/infra/main.tf, and a future services/payments/infra/main.tf not yet created. An enumerated directory list such as paths: ["modules/", "envs/staging/", "services/billing/infra/**"] would miss the new location until manually updated, which is the quarterly failure mode the forensics file highlights.
Decision chain: type-scattered governance calls for an extension glob rather than a directory prefix. The documentation's /.ts example generalizes to any extension, and /.tf follows the same form. The rule should use a positive extension glob plus a rooted subtree alternative for the single canonical terraform/ prefix, so both whole-tree and subtree cases are covered without relying on an unverified negation syntax.
Committed file:
---
paths:
- "terraform/**/*"
- "**/*.tf"
- "infrastructure/**/*"
- "**/*.tfvars"
---
# Terraform Conventions
- Use `snake_case` for all resource names.
- Tag every resource with `environment` and `team` labels.
- Never hardcode AMI IDs, use data sources.
- All modules must have `variables.tf`, `outputs.tf`, and `README.md`.
- Pin provider versions and reference a remote backend, never local state.What this proves: terraform// matches terraform/networking/vpc.tf at any depth, /.tf matches services/billing/infra/main.tf even though it lives outside the conventional prefix, and the same pattern will match the future services/payments/infra/main.tf without an update. The failure boundary is extension precision: changing /.tf to terraform//.tf would drop the billing infra file, while widening to / would govern non-Terraform files. All patterns in this file map to documented rows src// and */.ts.
Worked production examples: Example 3: Rule set split by topic for ownership and review
Goal: decompose a bloated root CLAUDE.md that has grown past 800 lines mixing API design, testing, deployment, and frontend style into focused files that each have one owner and one review surface.
Starting point: one CLAUDE.md holding every convention. The documentation advises targeting under 200 lines per file and using path-scoped rules when instructions grow large, because splitting into imports alone keeps text always loaded. The forensics file documents that contradictions between mixed concerns in a single file cause arbitrary selection by the model.
Target layout after the split:
repo/
CLAUDE.md
.claude/
rules/
testing.md
api-conventions.md
terraform.md
frontend.mdFile CLAUDE.md becomes a short index for universal standards and imports always-loaded shared material:
# Project Standards
Architecture
- Services in
src/api/, components insrc/components/. - State in Zustand stores, not component
useStateexcept for ephemeral UI.
Shared Reference
- Shared linting rules @../shared/linting-rules.md
Commands
- Build
npm run build, testnpm test, lintnpm run lint.
File .claude/rules/api-conventions.md: markdown --- paths: - "src/api/*/" - "/routes//*" - "*/.controller.ts" --- # API Conventions - All endpoints return { data, error, metadata }. - Use Zod schemas for request validation at the handler boundary. - Log request ID on every error response. - Rate limiting configuration must be explicit, not inherited from defaults. - Include OpenAPI documentation comments for every handler.
File .claude/rules/terraform.md is the same infrastructure file from Example 2, and .claude/rules/testing.md is the testing file from Example 1. What this proves is ownership. A change to API validation policy diffs in api-conventions.md alone, and a change to Terraform tagging diffs in terraform.md alone, which matches the documentation's advice to keep each file to one topic with a descriptive name such as testing.md or api-design.md and to organize into subdirectories when helpful. The failure avoided is the single-file churn where every team touches the same root file and where irrelevant conventions dilute attention during unrelated edits.
Token shape after the split: a session editing src/components/Button.tsx loads CLAUDE.md and frontend.md if a frontend rule exists, but does not load testing.md, api-conventions.md, or terraform.md. A session editing src/api/auth.test.ts loads both testing.md and api-conventions.md because its path matches both globs, paying the sum of the two bodies. That additive cost is expected for overlapping scopes, and since no precedence merges distinct files, the two guidance sets must be written to be complementary rather than contradictory.
Commands: Example 4: Alternative configuration style with alwaysApply and description-driven selection
Goal: illustrate the alternative vocabulary that appears in community material and map it byte-for-byte to the canonical Claude Code form, with clear marking of what is documented and what is unverified.
Alternative style as it appears in the forensics evidence:
---
alwaysApply: false
description: "Terraform infrastructure conventions"
globs:
- "**/*.tf"
- "terraform/**/*"
---
# Terraform Conventions
- Use `snake_case` for resource names.
- Tag every resource with `environment` and `team`.Interpretation against the documented mechanism: there is no .mdc file format in Claude Code, and the real primitives are CLAUDE.md, @path imports, and claudeMdExcludes. The documented frontmatter key is paths. The alternative style's globs is therefore the same intent as paths expressed under a different tool's vocabulary, and alwaysApply: false with a path list is the same intent as presence of paths. Conversely, alwaysApply: true with no path list is the same intent as absence of paths, meaning always loaded.
Canonical translation for Claude Code:
---
paths:
- "**/*.tf"
- "terraform/**/*"
---
# Terraform Conventions
- Use `snake_case` for resource names.
- Tag every resource with `environment` and `team`.The description field in the alternative style is described as an intent trigger that lets the model decide whether to load based on its reading of the session's purpose. That is probabilistic application, the same class as a prose header such as Apply the following only when working on Terraform files. The documented paths gate is structural, evaluated against the file path before context is assembled, so the model does not need to infer intent. Candidates should prefer the structural gate for path-based conventions and treat description-driven selection as insufficient when token exclusion is required, marking the alternative field names as not independently confirmed for Claude Code until verified against current documentation.
Commands: Example 5: The directory-memory approach it replaces
Goal: show the file layout that path-specific rules replace, so the trade-off against CLAUDE.md hierarchy choices is concrete.
Replaced layout: one CLAUDE.md per directory to cover scattered tests.
repo/
src/
components/
CLAUDE.md
Button.tsx
Button.test.tsx
api/
CLAUDE.md
auth.ts
auth.test.ts
utils/
CLAUDE.md
format.ts
format.test.ts
pages/
dashboard/
CLAUDE.md
Dashboard.tsx
Dashboard.test.tsxEach CLAUDE.md in this tree would need to carry a copy of the same testing conventions:
# Test Conventions for this directory
- Use `describe` and `it` blocks with descriptive names.
- Each test file must have at least one happy path and one error case.
- Mock at the module boundary.What this layout costs: every new feature directory added each week needs a new CLAUDE.md with the same text, every convention change must be applied across 50 copies, and drift is inevitable when some copies are updated and others are not. The directory-level mechanism itself is real and documented, subdirectory CLAUDE.md files do load lazily when their directory is touched, and in a true per-package monorepo such as packages/api/CLAUDE.md versus packages/web/CLAUDE.md that mechanism is the tightest fit. The point is axis: conventions locked to one package directory belong in that directory's CLAUDE.md, conventions for one file type spread across many directories belong in one .claude/rules/ file with a whole-tree glob.
Replacement with a single conditional rule:
---
paths:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.spec.ts"
- "**/*.spec.tsx"
---
# Test Conventions
- Use `describe` and `it` blocks with descriptive names that read as sentences.
- Each test file must have at least one happy path and one error case.
- Use factory functions for test data, not inline object literals.
- Mock external services at the module boundary, not individual functions.
- Assert behaviour, not implementation details.One file, one maintenance point, universal coverage including future directories, and zero tokens when editing src/api/auth.ts alone. The observable outcome that proves the swap succeeded is /context showing the rule for test files and not for non-test files, which is the same diagnostic the build exercise uses when it asks to compare token footprint between an always-loaded root file and split path-specific rules.
Build exercise material
The exercise below mirrors the reference page build steps but adds exact verification commands and the observable output that proves each step worked. Every path and command is literal and can be run in a fresh checkout.
Build exercise material: Exercise 0: Establish the baseline directory
Create the rule and support directories, then confirm discovery.
mkdir -p .claude/rules src/components src/api terraform
cat .claude/rules/testing.mdObservable outcome: cat prints the file you are about to create. If the file does not yet exist, the error confirms the path is clean for creation rather than an accidental overwrite.
Build exercise material: Exercise 1: Write the testing rule with a whole-tree extension glob
Create .claude/rules/testing.md with the documented paths field. The three-extension set below covers the reference page's exercise requirement of at least three conventions across naming, assertions, and mocking.
---
paths:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.spec.ts"
---
# Test Conventions
- Use `describe` and `it` blocks with descriptive names that read as sentences.
- Each test file must have at least one happy path and one error case.
- Use factory functions for test data, not inline object literals.
- Mock external services at the module boundary, not individual functions.
- Assert behaviour, not implementation details.This block is valid because every pattern is an instance of the documented /.ts class. Brace expansion form /.{test,spec}.{ts,tsx} would also be valid, expanding to multiple patterns within the 1,000-expansion budget.
Verification after creation:
head -6 .claude/rules/testing.md
Observable outcome: the first six lines show the opening ---, the paths: key, and the three glob entries. If the frontmatter is missing or the key is misspelled as globs or path, this check catches the error before relying on conditional loading.
Build exercise material: Exercise 2: Write the API rule with subtree globs
Create .claude/rules/api-conventions.md with directory-subtree patterns that scope by containment.
---
paths:
- "src/api/**/*"
- "**/routes/**/*"
---
# API Conventions
- All endpoints return `{ data, error, metadata }` response shape.
- Use Zod schemas for request validation at the handler boundary.
- Log request ID on every error response.
- Rate limiting configuration must be explicit, not inherited from defaults.These patterns map to the documented src// directory class and the /routes// variant. src/api//* matches src/api/auth.ts and src/api/v2/handlers/orders.ts but not src/services/billing.ts, which is the path-versus-content distinction the forensics file stresses: a glob is evaluated against the path string, not against file contents.
Build exercise material: Exercise 3: Write the infrastructure rule with combined type and subtree globs
Create .claude/rules/terraform.md with both a subtree prefix and a whole-tree extension, so the rule survives directory growth.
---
paths:
- "terraform/**/*"
- "**/*.tf"
---
# Infrastructure Conventions
- State files must reference remote backends, never local.
- Use workspaces for environment separation.
- Every module must be versioned with a CHANGELOG.
- Tag every resource with `environment` and `team`.terraform// ensures every file under the conventional prefix is covered regardless of extension, while /.tf guarantees that a future services/payments/infra/main.tf outside terraform/ is still governed. Both patterns are documented forms.
Build exercise material: Exercise 4: Verify conditional loading for a test file
Place a representative test file and then edit it in a session.
mkdir -p src/components
cat > src/components/Button.test.tsx <<'EOF'
import { describe, it, expect } from "vitest";
import { Button } from "./Button";
describe("Button", () => {
it("renders with the correct label", () => {
expect(true).toBe(true);
});
});
EOFIn a running Claude Code session, open src/components/Button.test.tsx for reading or editing, then run the diagnostic:
# Inside the Claude Code session
/contextObservable outcome that proves the mechanism: under Memory files, the output lists .claude/rules/testing.md as loaded. The files .claude/rules/api-conventions.md and .claude/rules/terraform.md do not appear, confirming that conditional loading excluded them and that prose headers did not keep them invisible. The documentation identifies /context as the check for which files loaded into the current session.
Build exercise material: Exercise 5: Verify the complementary API loading
Place a representative handler and switch context.
mkdir -p src/api
cat > src/api/handler.ts <<'EOF'
export async function handler(req: Request): Promise<Response> {
return Response.json({ data: null, error: null, metadata: {} });
}
EOFOpen src/api/handler.ts in the same session, then run /context again.
Observable outcome: .claude/rules/api-conventions.md appears as loaded while .claude/rules/testing.md and .claude/rules/terraform.md are absent. This confirms that switching the edited file swaps which paths gates match, and that overlapping scopes such as editing src/api/auth.test.ts would correctly show both testing.md and api-conventions.md simultaneously, with additive cost.
Build exercise material: Exercise 6: Compare token footprint between always-loaded and path-scoped composition
Move the same conventions into root CLAUDE.md temporarily to observe the baseline:
# Root CLAUDE.md with everything always loaded
Test Conventions
- Use
describeanditblocks with descriptive names. - Each test file must have at least one happy path.
API Conventions
- All endpoints return
{ data, error, metadata }.
Terraform Conventions
- Tag every resource with
environmentandteam.
Run /context while editing a neutral file such as src/utils/format.ts that matches none of the path-scoped patterns. With the bloated root file, /context shows the full convention text in the always-loaded memory files. Restore the split layout from Exercises 1 through 3 and run /context again on the same neutral file. The path-specific rules are absent, leaving only the lean CLAUDE.md. The token count for loaded configuration is measurably smaller in the second run, not by an official percentage factor but by the simple absence of the irrelevant rule bodies, which is the documented effect of scoping instructions to matching files. The documentation's warning that CLAUDE.md over 200 lines reduces adherence and over 4 MiB is skipped is the sizing anchor for this comparison, not a community arithmetic claim.
Terraform Conventions: Exercise 7: Fix a missing gate and verify the correction
Simulate the common mistake from the forensics file where a rule under .claude/rules/ lacks frontmatter and therefore loads unconditionally.
cat > .claude/rules/ungated.md <<'EOF'
# Unscoped Conventions
- This file has no paths gate and therefore loads for every session.
EOFRun /context on any file. The ungated file appears as loaded, confirming the unconditional behavior documented for rules without paths. Fix it by adding the gate:
cat > .claude/rules/ungated.md <<'EOF'
---
paths:
- "**/*.md"
---
# Scoped Conventions
- This file now loads only for markdown files.
EOFRun /context again while editing src/components/Button.tsx. The file is absent. While editing docs/guide.md it appears. That before-and-after pair is the observable proof that the presence of paths is the gate, not the directory location.
Terraform Conventions: Exercise 8: Test bracket and brace edge cases
Verify that literal bracket handling matches documentation.
mkdir -p "photos [2024"
touch "photos [2024/shot.jpg"
cat > .claude/rules/photos.md <<'EOF'
---
paths:
- "photos [2024/**"
- "photos \\[2024/**"
---
# Photo Handling
- Do not rewrite generated captures during routine edits.
EOFRun /context while reading photos [2024/shot.jpg. The first pattern photos [2024/ is invalid as a bracket expression and matches nothing, while the escaped photos \[2024/ is required to match a literal [. The rule's other patterns keep working even when one is invalid, which changed from a full Read failure in versions before 2.1.207. This exercise proves that single-character syntax errors in globs silently disable one pattern rather than the whole rule, which is why verification against real repository paths is needed before relying on a rule in review automation.
Terraform Conventions: Common failure modes during these exercises
- Key misspelling: writing
globs:orpath:instead ofpaths:leaves the file unconditional, so it appears in every/contextwhere it should have been absent. The forensics file flags this as the field-name trap where communityglobsspelling is mistaken for canonical. - Directory-bound under-match: writing
terraform/instead ofterraform//missesterraform/modules/networking/vpc.tfbecause*does not cross separators, while is required for depth. - Assuming negation: writing
!/__generated__/insidepathsand expecting exclusion, when the documentation providesclaudeMdExcludesinsettings.jsonas the exclusion primitive and does not document!forpathsnot independently confirmed. - Import confusion: adding
@./standards/testing.mdinsideCLAUDE.mdto simulate conditional loading, when imports expand eagerly at launch and therefore keep testing conventions always present. - Source exclusion: excluding
projectfrom the SDKsettingSourcesand concluding thatpathsevaluation is broken, when the rule was never loaded because its tier was excluded.
Terraform Conventions: Quick reference table for the build
| Step | File | Frontmatter paths | Expected presence in /context when editing |
|---|---|---|---|
| 1 | .claude/rules/testing.md | ["/.test.ts", "/.test.tsx", "*/.spec.ts"] | Present only for /.test. and /.spec. files |
| 2 | .claude/rules/api-conventions.md | ["src/api//", "/routes//"] | Present only for src/api/ subtree files and routes directories |
| 3 | .claude/rules/terraform.md | ["terraform//", "/.tf"] | Present for any .tf file or anything under terraform/ |
| 4 | Any neutral file such as src/utils/format.ts | None of the above match | No rule file appears, only root CLAUDE.md |
| 5 | Overlapping file such as src/api/auth.test.ts | Matches both testing and API lists | Both testing.md and api-conventions.md appear together |
This table is the minimal mental model to carry into the exam. Any scenario about co-located tests scattered across many directories maps to the first row. Any scenario about a trio of platform roots such as ios/, android/, and backend/ maps to directory-level CLAUDE.md or to a positive paths entry such as ios/* when expressed as a rule, not to a whole-tree type glob. Any scenario about a universal baseline that must cover every type including Dockerfile and .yaml maps to project-level CLAUDE.md or managed policy, not to an enumerated paths list.
Mechanism and API surface
- .claude/rules with paths frontmatter
- YAML frontmatter with paths array of globs such as star star slash star dot test dot ts for file type or src slash api slash star star for directory scope, loaded only when a read matches, one file covers cross directory conventions.
- Glob semantics and root
- star single segment, star star any depth including zero, question single char, bracket expressions, root is project root, so star star slash star dot test dot tsx matches every test file anywhere in the repo.
- Universal form without frontmatter
- No frontmatter or empty paths means loaded for every session. Correct for universal standards, wasteful for file type specific rules where conditional form saves tokens.
- Incremental lazy loading composition
- Walk up chain eager at launch, subdirectory CLAUDE.md and matching path rules lazy on read, once loaded rules stay in session, unmatching patterns never contribute in that session.
- claudeMdExcludes companion
- Array in .claude/settings.json of globs against CLAUDE.md file paths to skip discovery, such as star star slash experimental slash star star, for noisy monorepos where another team's CLAUDE.md keeps surfacing.
- Import composition for shared standards
- Shared standards imported via @path from multiple path scoped rules keep the unconditional part in one place while each rule keeps its conditional body focused on its glob.
The decision rules in play
Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.
Rule files live in `.claude/rules/` and are scoped by a `paths` array in YAML frontmatter
Each rule file is a Markdown file placed inside the .claude/rules/ directory at the project root. Its first block is YAML frontmatter delimited by --- lines. Inside that block the field paths holds an array of glob strings. When a developer session edits a file whose path matches any entry in paths, the rule file body is loaded into context automatically. When no edited file matches, the rule stays unloaded. The body below carries the conventions.
A canonical example for infrastructure conventions looks like this:
Editing a file at terraform/networking/vpc.tf matches terraform// and also /.tf, so this file loads. Editing src/components/Button.tsx matches none of the three strings, so it does not. The field name is paths in the tested material, appearing in most items as paths: ["/.test.ts", "/.test.tsx"] or paths: ["src/api/*/"]. A minority of items show the legacy globs spelling, which the guide treats as the same concept. Evidence shows paths as the dominant field.
Directory layout for this rule:
---
paths: ["terraform/**/*", "**/*.tf", "infrastructure/**/*"]
---
# Infrastructure Conventions
- Use `snake_case` for all resource names
- Tag every resource with `environment` and `team`
- Never hardcode AMI IDs, use `data` sources
- Every module must have `variables.tf`, `outputs.tf`, and `README.md`repo/
CLAUDE.md
.claude/
rules/
terraform.md
testing.md
api-conventions.md
terraform/
networking/
vpc.tf
src/
components/
Button.tsxThe feature exists to let a single file govern one file type wherever that type lives. Without it the only built-in alternatives are root CLAUDE.md which loads everything for every session, and directory-level CLAUDE.md which loads for every file inside one subtree. A path array scoped by glob bridges the gap: one file, one maintenance point, deterministic activation based on the file being edited rather than on the model inferring intent from prose.
Boundary. If the frontmatter is missing or empty, the rule is not conditional. Multiple evidence items state explicitly that a file without YAML frontmatter loads for every session, behaving like an extension of root CLAUDE.md. The boundary is therefore the presence of a valid paths array. Add it and the rule becomes gated, remove it and it becomes universal. The opposite case where this mechanism is intentionally not used is the universal standard. Input validation, no secrets in source, and legal headers that must apply to every file in the repository should not be placed behind any paths gate, because any omission in the glob list creates a hole. Those standards belong in the always-loaded project-level CLAUDE.md.
Recurring specifics. Field name paths dominates, with globs as a secondary spelling. Patterns that recur include terraform//, /.tf, /.tfvars, /.test.ts, /.test.tsx, /.spec.ts, /.spec.tsx, src/api//, /migrations//, db/migrations//.sql, /.py, /.ts, /.tsx, k8s//, /Dockerfile, and infra/terraform/*/. File locations that recur are .claude/rules/terraform.md, .claude/rules/testing.md, .claude/rules/api-conventions.md, and .claude/rules/db.md. Diagnostic tools that recur are /memory and /context to verify which rule files are currently loaded.
Proposal. prose-gated root sections with headers such as Apply the following only when working on Terraform files.
Why it attracts. it avoids creating new files and keeps everything in one place.
Why it fails. root CLAUDE.md is always loaded regardless of header wording, so tokens are always consumed and the application is probabilistic. Model behavior reports in the tested material.
- Changing
/.test.tstosrc/api//moves the axis from file-type to directory-subtree, which reverses whether new directories are auto-covered. Adding a missing frontmatter block changes the answer from conditional to always-loaded.
Glob patterns match file paths, not file contents
A glob is evaluated against the repository-relative file path string, not against file contents or rule file names. The string src/api// matches any path that begins with src/api/ followed by any depth of subdirectories and any filename. The string /.tf matches any path ending in .tf at any depth. A rule file named api-conventions.md with paths: ["src/api/*/"] does not apply because the name contains api, it applies only when the edited file path itself satisfies the glob.
A testing example shows the distinction:
The glob */.test.ts matches src/pages/dashboard/Dashboard.test.ts and lib/helpers/format.test.ts equally, because both paths end with the segment .test.ts. It does not match src/pages/dashboard/Dashboard.ts, even though that file's contents may mention tests.
---
paths: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"]
---
# Test Conventions
- Use `describe` and `it` blocks with sentence-style names
- Each file must cover one happy path and one error case
- Use factory functions for test data, not inline literals
- Mock at the module boundary, not per function
- Assert behavior, not implementationPath matching is deterministic and cheap. It can be evaluated before reading any file content, and it maps directly to the scoping question the developer actually has: which files should this guidance govern. Content matching would require reading every file to decide whether the rule applies, which defeats the token-efficiency goal and introduces ambiguity about which keywords trigger which rules.
Boundary. The nearby case where content matching is the right tool is codebase search during development, not rule loading. When a developer needs to find every file that imports @company/auth or references the table fact_revenue_daily, Grep is correct because it searches inside file contents. Glob is correct when the task is to list every file whose name matches /.tf or every migration file at /migrations/.sql. Confusing the two produces both false positives and large false negatives, as multiple evidence items show.
Recurring specifics. Tool names that recur are Glob, Grep, and Read. Path patterns that recur include src/api//, src/services/billing.ts, config//.json, and */fact_revenue_daily as an example of a wrong Glob for a content task. The anti-pattern that recurs is using Glob with fact_revenue_daily* to find content references, or using Grep to find files by extension.
Proposal. the rule file name determines scope, such as api-conventions.md automatically covering any file with api in its path.
Why it attracts. file names are visible and suggest intent.
Why it fails. scope is determined only by the paths array, not by the rule file name. A file at src/services/billing.ts does not match src/api/*/ simply because the rule file is.
- Narrowing
/.test.to/.test.tsdrops.test.tsxfiles from coverage, which changes the answer for React component tests. Expandingsrc/api//to/api/*/widens from a rooted prefix to any subtree namedapi, coveringservices/billing/api/handlers.tsas well.
Recursive `` matches any depth and provides whole-tree coverage from a single file
In a glob, a single matches any characters within one path segment, but does not cross a / separator. The double-star * matches zero or more path segments, including crossing separators. That difference is what makes whole-tree coverage possible.
- *.tf matches main.tf in the current directory but not terraform/main.tf - /.tf matches terraform/main.tf but not terraform/modules/networking/vpc.tf - */.tf matches all three: main.tf, terraform/main.tf, and terraform/modules/networking/vpc.tf - terraform/*/ matches every file under terraform/ at any depth, such as terraform/modules/networking/vpc.tf and terraform/environments/production/main.tf - src/api/*/ matches src/api/auth.ts and src/api/v2/handlers/orders.ts but not src/services/billing.ts
A canonical project tree that the tested material uses for testing conventions shows why `` is required:
A single rule at .claude/rules/testing.md with paths: ["/.test.ts", "/.test.tsx", "/*.spec.ts"] matches every test file in that tree regardless of which subfolder it sits in. Without, the rule would need an entry for each directory depth, which is not maintainable and breaks when new directories are added.
src/
components/
Button.tsx
Button.test.tsx
api/
auth.ts
auth.test.ts
utils/
format.ts
format.test.ts
pages/
dashboard/
Dashboard.tsx
Dashboard.test.tsxConventions that follow a file type rather than a fixed directory must survive directory creation, renames, and moves. Recursive matching decouples the convention from the directory layout. The same pattern */.test.tsx remains correct whether tests live in four directories today or in forty after a feature reorganization.
Boundary. The opposite case is subtree-confined guidance where recursive whole-tree matching is too broad. A pattern /.swift matches Swift files everywhere, but a team that strictly confines Swift to ios/ should use ios// or ios//.swift with frontmatter that reflects that confinement. Using /.swift in that team would over-match if Swift files later appear outside ios/ due to a tooling artifact, while using ios/*/ would under-match if the team later adopts cross-cutting Swift files.
Recurring specifics. Patterns that recur include /.test.ts, /.test.tsx, /.spec.ts, /.spec.tsx, /.tf, /.py, /.sql, /, /migrations//, and terraform//. Directory names that recur include src/components/, src/api/, src/utils/, src/pages/dashboard/, features/, services/auth/migrations/, and experiments/*/native/.
Proposal. enumerating every current directory that holds Terraform files, such as paths: ["modules/.
Why it attracts. it lists the known present and feels precise.
Why it fails. new infrastructure folders are created most quarters, and every new folder requires a manual update or it goes uncovered. A file-type glob */.tf.
- Replacing
/.tfwithterraform//.tfnarrows from every.tffile in the repo to only those underterraform/, which missesservices/billing/infra/main.tf. Replacingterraform/*/withterraform/*narrows to the top level only and missesterraform/modules/networking/vpc.tf.
Extension globs such as `/.tf` and `/.py` scope by language regardless of directory
Extension globs match the suffix of the path string. /.tf matches any file whose last three characters are .tf at any depth. /.py matches any Python file, /.rs any Rust file, /.sql any SQL file, and /.tsx any React component test file. When a rule file lists paths: ["/.py"], every edit to a Python file triggers the rule regardless of which feature directory contains the file.
A polyglot setup that the tested material repeats uses separate rule files per language:
Editing kernels/sort.rs loads only the Rust file, editing jobs/ingest.py only the Python file, and editing analytics/revenue.sql only the SQL file. The globs follow type, not location.
---
paths: ["**/*.rs"]
---
# Rust Conventions
- Use `snake_case` for functions, `UpperCamelCase` for types
- Prefer `Result` over panics at module boundaries
- All `unsafe` blocks require a safety comment---
paths: ["**/*.py"]
---
# Python Conventions
- Follow PEP 8, enforce with `ruff`
- Type hints required on all public functions
- Never use bare `except`---
paths: ["**/*.sql"]
---
# SQL Conventions
- Use `snake_case` for identifiers
- Every migration must have a rollback section
- Never `SELECT *` in production codeWhen a language is interleaved across many feature directories rather than isolated in one subtree, only a type glob can cover it. the tested material describes a computational-biology platform organized by scientific capability where Rust files live under kernels/, bindings/, and several experiments//native/ folders, and SQL files under db/, analytics/, and assorted experiments//queries/ paths. A directory list would need constant updates, while /.rs and /.sql remain correct as the layout evolves.
Boundary. The nearby opposite case is the tightly confined polyglot where each language is already rooted in one top-level directory, such as ios/ for Swift, android/ for Kotlin, and backend/ for Python. There the tighter scopes ios//.swift or even ios/ are preferable to /.swift, because they express the actual architectural constraint and avoid accidentally covering files that should not be governed by those conventions.
Recurring specifics. Language extensions that recur include .py, .ts, .tsx, .rs, .go, .sql, .tf, .swift, .kt, .c, .h, .yaml, .yml, .dockerfile, and .md. File-type rule names that recur include rust-conventions.md, python-conventions.md, sql-conventions.md, terraform.md, and react-hooks.md. Directory roots that recur include kernels/, bindings/, experiments/*/native/, db/, analytics/, services/, and web/.
Proposal. directory-bound globs such as paths: ["kernels/.
Why it attracts. it lists where Rust currently lives.
Why it fails. it omits any future Rust location and requires updates as the layout grows. A type glob */.rs
When it would be right. the convention must follow the language everywhere.
- Switching
/.pytoservices//.pynarrows from every Python file to only those underservices/, missingtools/scripts/migrate.py. Switching/.tsxto/.tswidens to include plain TypeScript files and drops the JSX-specific convention from non-JSX files.
Conditional loading means a rule consumes zero tokens when no matching file is in play
Path-scoped rules are loaded lazily. The session starts with only the always-loaded context, which includes root CLAUDE.md and any @import inlined files. A rule file in .claude/rules/ with a paths array is not read until an edit or read touches a file whose path satisfies at least one pattern in the array. Until that moment the rule contributes no tokens.
the tested material illustrates the lifecycle with a Terraform rule:
A developer who spends the day editing React components in src/components/ never triggers the Terraform rule, so the token cost for that developer is zero for that file. A developer editing terraform/modules/networking/vpc.tf triggers it and the conventions become visible for that file's edits.
Verification is done with the diagnostic commands /memory or /context, which list which rule files are currently loaded. Editing a test file should show .claude/rules/testing.md as loaded while .claude/rules/api-conventions.md and .claude/rules/terraform.md are absent.
---
paths: ["terraform/**/*", "**/*.tf"]
---
# Terraform Conventions
- Pin provider versions
- Prefix resource names with the team slug
- Every resource must have `tags` with `environment`Token budget is shared with useful context such as project architecture and file contents. Loading every convention for every session reduces the space available for the actual task and dilutes attention, which the tested material links to inconsistent application of specific conventions. Conditional loading keeps the budget focused on conventions that are relevant to the current files.
Boundary. The opposite case is the always-loaded standard. A security baseline such as input validation, no secrets in source, and never logging PHI must be visible regardless of which file is edited. Gating that baseline behind paths: ["/.py", "/.go", "*/.ts"] creates holes for any new file type, as the Dockerfile and .yaml incident in the tested material demonstrates. Those standards belong in root CLAUDE.md or another always-loaded location.
Recurring specifics. Diagnostic commands that recur are /memory and /context. Token-related phrases that recur include token-efficient, token budget, context window, attention dilution, and irrelevant context. The numeric bleed rate that recurs is roughly 15 to 18 percent when root CLAUDE.md prose gating is used instead of path scoping. The token bloat pattern that recurs is 50 thousand tokens of database conventions loaded during frontend-only sessions.
Proposal. path-scoped rules still load but are compressed, or are cached and therefore free.
Why it attracts. it acknowledges a cost problem while minimizing the fix.
Why it fails. the actual mechanism is not compression or caching, it is not loading at all when no path matches. Path-scoped rules that do not match consume zero tokens, not fewer tokens.
- Moving a database runbook from root
CLAUDE.mdinto.claude/rules/db.mdwithpaths: ["db/migrations//"]changes the cost from always-loaded to file-triggered. Moving a universal baseline out of rootCLAUDE.mdinto.claude/rules/security.mdwith an enumerated list such aspaths: ["/.py", "/.go", "/.ts"]changes it from universal to gated and introduces holes.
Root `CLAUDE.md` is always loaded and therefore wastes tokens for file-type-limited guidance
Root CLAUDE.md at the repository root and at .claude/CLAUDE.md is included in every session at startup. Its contents are concatenated into the system context before any file is edited. Any guidance placed there, regardless of header wording, consumes tokens for every task.
Typical structure that causes the problem:
Inside that CLAUDE.md the team may write:
Both sections are loaded for every session, even when the developer edits only src/components/Button.tsx. The header phrasing does not prevent loading, it only asks the model to infer applicability.
repo/
CLAUDE.md # 800 lines: Terraform + React + API + testing + deployment
.claude/
rules/
terraform/
src/
components/
api/## Terraform Conventions
Apply the following only when working on Terraform files.
- Use `snake_case` for resource names
- Tag every resource with `environment` and `team`
## React Conventions
Apply the following only when working on React files.
- Use functional components with hooks
- Never use `div` for mobile componentsRoot memory is designed for universal standards that apply to all code, not for file-type-limited conventions. Once a section is written in root CLAUDE.md it becomes unconditional. The model may still apply Terraform naming prefixes to TypeScript files, which the tested material records at a 15 percent misapplication rate in mixed sessions, because the text is present in context and the gating is probabilistic rather than structural.
Boundary. The boundary is scope. If a standard must govern every file, root CLAUDE.md is the correct home. Security baselines, commit message formats, branch naming, and cross-service API contracts are universal and belong there. If a convention governs one file type across many directories, moving it from root CLAUDE.md into a path-scoped rule with paths: ["*/.tf"] cuts its token cost to zero for sessions that do not touch that type.
Recurring specifics. Line counts that recur include 500, 600, 650, 800, 850, 900, and 1400 lines for bloated root files. Phrases that recur include always loaded, burns tokens, loads for every session, and regardless of which files you edit. Misapplication rates that recur include roughly 15 percent for Terraform prefixes on non-Terraform files and 18 percent for Python versus SQL cross-application.
Proposal. adding sharper header wording or an XML wrapper to fix the scoping.
Why it attracts. it keeps the single-file workflow unchanged.
Why it fails. wording does not change loading, only application, and application remains probabilistic. Evidence shows that sharpening headers reduces bleed slightly but does not stop irrelevant content from occupying context.
- Moving Terraform conventions from root
CLAUDE.mdinto.claude/rules/terraform.mdwithpaths: ["*/.tf"]changes them from always-loaded to file-conditional. Splitting rootCLAUDE.mdinto@importmodules without addingpathsgates keeps them always-loaded and changes only maintainability.
Directory-level `CLAUDE.md` is subtree-bound and forces duplication when matching files are scattered
A CLAUDE.md placed inside a subdirectory applies to files within that subdirectory tree. A file at terraform/CLAUDE.md governs files under terraform/, a file at src/api/CLAUDE.md governs files under src/api/, and a file at k8s/CLAUDE.md governs files under k8s/. The governance is by directory containment, not by file type.
Tree that shows the limitation:
To cover testing conventions with directory-level files, the team would need a CLAUDE.md in src/components/, another in src/api/, another in src/utils/, and another in src/pages/dashboard/, plus one in every new feature directory added each week. A single rule file at .claude/rules/testing.md with paths: ["/.test.ts", "/.test.tsx"] covers all four locations and any future directory from one file.
repo/
CLAUDE.md
src/
components/
Button.tsx
Button.test.tsx # needs testing conventions
api/
auth.ts
auth.test.ts # needs testing conventions
utils/
format.ts
format.test.ts # needs testing conventions
pages/
dashboard/
Dashboard.tsx
Dashboard.test.tsx # needs testing conventionsDirectory scoping assumes the relevant files are co-located under one subtree. When the relevant files are a file type that is co-located with the code it tests, the type is scattered by design. Each new directory inherits the cost of adding, copying, and updating another CLAUDE.md. Drift is inevitable, because some copies are edited while others are not, and reviewers report inconsistent application where stale copies are applied to production files or missing entirely for new test files.
Boundary. The nearby case where directory-level CLAUDE.md is correct is platform confinement. A team with ios/, android/, and backend/ as strict roots where Swift, Kotlin, and Python never leave their respective roots is correctly served by ios/CLAUDE.md, android/CLAUDE.md, and backend/CLAUDE.md. The boundary is architectural confinement. If the convention must follow a type that appears in many places, use a type glob. If the convention must confine a platform to one subtree, use a directory file.
Recurring specifics. Directory examples that recur include terraform/, k8s/, services/auth/migrations/, features/, frontend/, backend/, ios/, android/, and tests/. Duplication costs that recur include 50 plus directories, 40 feature directories, and weekly addition of new directories. The phrase that recurs is massive maintenance burden and inevitable drift as some copies fall behind.
Proposal. placing a copy of the convention in every directory and using @import inside each copy to reference a single canonical file.
Why it attracts. it acknowledges duplication and tries to patch it with referencing.
Why it fails. it still requires creating and maintaining a file in every directory and updating every import when the canonical file moves. A single path-scoped.
- Replacing a type glob
/.test.with a directory globfeatures/changes from file-type matching to subtree matching, which contaminates non-test files underfeatures/and fails to cover test files outside it. Replacing per-directoryCLAUDE.mdcopies with a single.claude/rules/testing.mdwithpaths: ["*/.test.*"]collapses many files to one and makes coverage future-proof.
Prose instructions in root `CLAUDE.md` cannot deterministically gate which section applies
Natural language in root CLAUDE.md can describe intent, such as Apply the following only when editing Terraform files or Consult the tooling section before selecting a tool, but the model still receives the entire text in context and must decide probabilistically whether the description matches the current files. Path-scoped rules with paths globs are evaluated by the system before context is assembled, so the gating is structural.
the tested material contrasts the two directly. A root section with a clear heading and the instruction Apply only when working in ios/ followed by Swift conventions still produces roughly 18 percent cross-application to Kotlin and backend files. Replacing that section with .claude/rules/ios.md gated by paths: ["ios/"] or by type paths: ["/*.swift"] removes the cross-application because irrelevant sessions do not contain the text at all.
Language alone does not make a section invisible. The text remains in context and competes for attention with the relevant conventions. In long files the model also dilutes attention and applies guidance from one topic while working in an unrelated area, which the tested material links to 800 plus line files mixing unrelated concerns.
Boundary. Prose gating is acceptable only when the guidance is truly universal and the header exists for navigability rather than scoping. Security baselines, commit templates, and architecture notes that apply to every edit can be sectioned with headers and remain in root CLAUDE.md. The boundary is whether the gating must exclude tokens from unrelated sessions. If yes, a structural gate with paths is required.
Recurring specifics. Phrases that recur include probabilistic, inference, always loaded, rely on Claude to infer which section applies, and does not reduce token usage. Header examples that recur include Apply the following only when working on Terraform files and Apply the following only when working in ios/.
Proposal. wrapping conventions in an XML tag or adding an @import comment that says apply only when reviewing infrastructure files.
Why it attracts. it feels like adding structure.
Why it fails. both are still inlined eagerly, so the content is always loaded and the comment is still probabilistic text.
- Changing a root
CLAUDE.mdsection into a.claude/rules/file withpaths: ["*/.tf"]converts a probabilistic prose gate into a structural path gate. Adding a bold header and table of contents to an 800 line root file improves navigation but keeps bleed and token cost unchanged.
`@import` inlines eagerly and does not make loading conditional
The directive @path inside CLAUDE.md references another Markdown file that is read inline at load time. The syntax that recurs is @./standards/api.md, @./standards/testing.md, and @./conventions/api.md. At session start every imported file is resolved and concatenated into the loaded memory. No evaluation of which files are being edited is performed at import time.
Comparison of intents:
- @import plus root CLAUDE.md for modular always-loaded guidance that should be present in every session but maintained as separate files. - paths frontmatter inside .claude/rules/ for conditional loading that should be present only when matching files are edited.
A refactor that splits an 800 line root file into @import modules keeps all conventions always loaded but makes the root file short and navigable, with each topic owned in its own file. A refactor that moves Terraform conventions into .claude/rules/terraform.md with paths: ["*/.tf"] makes them load only for Terraform file edits, which is a different effect.
Eager inlining is the correct behavior for universal composition, because the imported file is needed in every session regardless of which subdirectory is active. It is the wrong tool when the goal is to reduce per-session tokens by excluding irrelevant conventions, because eagerness loads the content even for sessions that never touch the relevant file type.
Boundary. @import is the correct choice when every section genuinely applies to every session and the team wants one file split into manageable pieces that are all always loaded. That scenario appears explicitly when the question states the file is unwieldy but every section applies everywhere. The boundary is the stated requirement. If any section should be excluded for some sessions, that section belongs in a path-scoped rule rather than an import.
Recurring specifics. Import syntax that recurs includes @./standards/api.md, @./standards/testing.md, @./conventions/api.md, and @import ./conventions/api.md as a conceptual variant. The eagerly-loaded property that recurs is described as loads eagerly and inlined at load time. The feature appears under references to project configuration and memory documentation.
Proposal. using @import to solve token bloat from always-loaded database conventions during frontend work.
Why it attracts. it does involve moving content out of root CLAUDE.md.
Why it fails. the imported database file is still loaded for frontend sessions, so token waste remains.
When it would be right. the goal is navigability of always-loaded content, not token.
- Adding
@importto a bloated root file without changing whether any section is conditional preserves token cost and changes only review granularity. Converting some imports into path-scoped rules while keeping universal standards as imports changes some sections from always-loaded to conditional.
A `.claude/rules/` file without frontmatter behaves as always-loaded
If a Markdown file is placed in .claude/rules/ with no YAML frontmatter, or with frontmatter that has no paths array, the system treats it as having no gate. The conventions inside load for every session, regardless of which files are edited.
the tested material states this explicitly for a file such as .claude/rules/testing.md with no frontmatter that loads even when editing API handler files, consuming tokens unnecessarily. The fix is to add the gate:
With that frontmatter the file loads only when a test file is being edited. Without it the same body's conventions are always loaded.
---
paths: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"]
---
# Test Conventions
- Use `describe` and `it` blocks with sentence-style names
- Each file must cover one happy path and one error caseThe directory alone does not create conditionality, the frontmatter does. Teams that assume that any file under .claude/rules/ is automatically scoped are surprised when API and Terraform conventions leak into frontend sessions. The gate must be written explicitly.
Boundary. A .claude/rules/ file with an intentionally broad gate such as paths: ["*/"] is also effectively always-loaded, because it matches every file. That is rarely the intended design. The nearby case where broad matching is correct is not a path-scoped rule but root CLAUDE.md itself or a canonical universal standards file referenced by @import, because those mechanisms express universal intent more clearly.
Recurring specifics. File examples that recur include .claude/rules/testing.md with no frontmatter, testing.md that has no paths field, and the fixing pattern paths: ["/.test.ts", "/.test.tsx", "*/.spec.ts"]. The diagnostic that recurs is a developer noticing testing.md loading when editing API handler files.
Proposal. moving the always-loaded rule out of .claude/rules/ into a directory-level CLAUDE.md inside the test folder.
Why it fails. it tries to add scoping by location. It fails when tests are scattered next to sources across many packages, because no single test folder exists to host the directory file. Adding paths frontmatter would be correct here.
- Adding
paths: ["/.test.ts", "/.test.tsx", "*/.spec.ts"]to an always-loadedtesting.mdmakes it conditional and immediately reduces token waste for non-test sessions. Removing that frontmatter from a conditional rule reverts it to always-loaded and restores the waste.
One file-type glob in one rule file replaces per-directory copies across any number of folders
A single pattern such as /.test.tsx or /migrations// evaluated against the edited file path can match files in any number of directories. One Markdown file at .claude/rules/testing.md with that pattern therefore covers every directory that contains matching files today and any directory that will contain them tomorrow, without creating additional configuration files.
Concrete layout before the fix:
After consolidation:
The rule body can contain the full testing convention, such as the four bullet items from the reference: factory functions for test data, module-boundary mocking, descriptive describe and it names, and behavior assertions. The single file is the canonical source.
repo/
src/
components/
CLAUDE.md # copy of testing conventions
Button.test.tsx
api/
CLAUDE.md # copy of testing conventions
auth.test.ts
utils/
CLAUDE.md # copy of testing conventions
format.test.tsrepo/
.claude/
rules/
testing.md # single file with paths: ["**/*.test.ts", "**/*.test.tsx"]
src/
components/
Button.test.tsx
api/
auth.test.ts
utils/
format.test.tsDuplication creates a coordination cost that grows with the number of directories and with the rate at which new directories appear. Changing a ban on snapshot tests or a requirement for accessibility assertions would require updating dozens of files and trusting that every new feature directory creator remembers to copy the current version. A single source with a whole-tree glob eliminates that coordination cost and makes the convention future-proof.
Boundary. The opposite case is subtree-confined guidance where the number of directories is small, fixed, and architecturally meaningful. Three platform roots ios/, android/, and backend/ that are owned by distinct teams and where files never leave their root are better served by directory-level files. Duplication cost is low when there is one directory per team and high when the type is co-located across dozens of directories.
Recurring specifics. Numbers that recur include 50 plus directories, 200 plus packages, 40 feature directories, and weekly addition of new directories. Patterns that recur include /.test.tsx, /.test.ts, /migrations//, and /.spec.ts. The maintenance symptom that recurs is stale copies applied inconsistently and forgotten copies for newly added directories.
Proposal. adding all test conventions to root CLAUDE.md to cover all directories from one file.
Why it attracts. it also uses one file.
Why it fails. root CLAUDE.md is always loaded for non-test sessions, wasting tokens and diluting attention for unrelated work. A conditional path-scoped rule with a whole-tree glob would be correct because it preserves the one-file maintenance.
- Changing
/.test.to/.test.tsdrops.test.tsxcoverage and silently excludes React component tests. Changing/migrations//.pyto/migrations//*widens from Python migration files to every file under migration directories.
Choose the scoping axis deliberately: file-type globs versus directory-subtree globs
Two different glob families express two different intents. File-type globs such as /.swift, /.py, and /.tf match by extension at any location. Directory-subtree globs such as ios/, backend/, and src/api// match by containment inside a rooted prefix regardless of extension.
Selection table:
- Type-scattered but location-unbounded guidance such as Terraform rules that appear under modules/, envs/staging/, and services/billing/infra/ and will appear in new infra folders: use paths: ["*/.tf"]. - Platform-confined guidance where every Swift file lives inside ios/ and never outside it: use paths: ["ios/"] or directory-level ios/CLAUDE.md. - Mixed cases such as /.swift versus ios/ when ios/ is the only Swift location today but a future tool may generate Swift elsewhere: the tighter ios/ avoids over-matching, but /.swift avoids under-matching. The correct choice depends on which risk the team documents as architectural intent.
An example that the tested material tests explicitly:
This file loads for any file under ios/, such as ios/Features/Payments/PayView.swift and ios/Scripts/build.py inside the same subtree. A file at backend/services/pay.py does not trigger it.
---
paths: ["ios/**"]
---
# Swift Conventions
- Use value types for models
- Prefer SwiftUI patterns for views
- Enforce strict access controlScoping on the wrong axis creates both holes and contamination. A directory list for Rust that enumerates kernels/, bindings/, and experiments//native/* misses any new Rust location outside those paths, while a type glob for a platform-confined codebase may apply mobile conventions to a file that the architecture says should not exist outside the platform directory. The axis choice must match whether the convention follows what the file is or where it lives.
Boundary. The boundary is stated in the exam summary: conventions locked to a directory subtree call for directory-level memory, conventions that span the whole tree by type call for .claude/rules/ type globs. Choosing the wrong axis causes over or under match. When a polyglot platform has three top-level roots ios/ with Swift, android/ with Kotlin, and backend/ with Python, and helper scripts stay inside their own root, the tested material marks directory-level files as correct, not type globs.
Recurring specifics. The axis phrase that recurs is file type versus directory location and conventions for one specific package directory versus conventions for a file type spread across many directories. The platform framing that tests this is ios/ plus android/ plus backend/ with 18 percent bleed when the wrong axis is chosen. The type framing that tests this is kernels/, bindings/, experiments/*/native/ with Rust.
Proposal. file-type globs for the ios/, android/, backend/ platform split, such as paths: [".
Why it fails. type globs are the dominant success pattern elsewhere. It fails for the platform case where the requirement is directory confinement and the team states that files do not appear outside their root..
- Switching
paths: ["/.swift"]topaths: ["ios/"]changes from any Swift file anywhere to any file underios/and widens to include non-Swift files insideios/. Switchingpaths: ["kernels/", "bindings/"]topaths: ["/.rs"]changes from an enumerated set of known locations to every Rust file including future locations.
Universal always-on standards belong in project-level `CLAUDE.md`, not behind a path gate
Project-level CLAUDE.md at the repository root or at .claude/CLAUDE.md is included in every session. It is the only location that guarantees that guidance is present regardless of which file is opened. Universal standards that apply to every edit, such as commit message format, architectural boundaries, secrets handling, and branch naming, belong there.
Structure that separates universal from conditional:
The always-loaded file holds what must never be missing. The path-scoped files hold what must be invisible until relevant.
repo/
CLAUDE.md # always loaded: security baseline, commit format, branch rules
.claude/
rules/
terraform.md # paths: ["**/*.tf"]
testing.md # paths: ["**/*.test.ts", "**/*.test.tsx"]
python-style.md # paths: ["**/*.py"]A universal standard that is gated behind a path misses every file type not listed in the gate. the tested material demonstrates this with a security baseline moved from root CLAUDE.md into .claude/rules/security.md with paths: ["/.py", "/.go", "/.ts", "/.tsx", "*/.sql"], after which edits to a Dockerfile and a .yaml logging config never received the baseline and produced a secrets and PHI exposure. Universal intent requires an ungated location.
Boundary. The nearby case where path gating is correct is platform or language specific style, such as React hook conventions scoped to */.tsx. Those conventions are intentionally not universal and should not appear during backend-only work. The boundary is breadth of applicability. If the rule must govern every file, do not gate it.
Recurring specifics. Universal examples that recur include commit message template, branch naming, use UTC for all timestamps, secrets handling, input validation on all external data, never logging PHI, and architectural notes about service boundaries. The failure example that recurs is a Dockerfile and .yaml not covered by an enumerated python and Go and TypeScript gate.
Proposal. broadening the gate to paths: [".
Why it attracts. it keeps the rule inside the same subsystem as other rules.
Why it fails. a */ gate is still a path match rather than a statement of universal intent, and the location suggests conditionality that the standard does not have. Project-level CLAUDE.md.
- Moving a universal baseline from root
CLAUDE.mdinto.claude/rules/security.mdwith any enumeratedpathslist changes it from guaranteed to gated and introduces holes. Changingpaths: ["*/"]in that rule back to rootCLAUDE.mdrestores the guarantee and makes intent explicit.
Mandatory baselines that must cover every file type cannot be put behind an enumerated glob list
An enumerated list such as paths: ["/.py", "/.go", "/.ts", "/.tsx", "*/.sql"] enumerates the extensions known today. Any file type not in the list is ungoverned. In a growing platform the next file type to appear may be Dockerfile without an extension, .yaml for configuration, .json for generated tooling output, or *.md for documentation that still contains secrets. The baseline that must cover every file is therefore not a good candidate for enumeration.
the tested material incident makes this concrete. After the baseline was gated by a five-extension list, edits to a Dockerfile that embedded a secret and edits to a .yaml logging configuration that recorded PHI were performed without the baseline in context, so the model did not apply input validation, secrets handling, or PHI rules. The new file types had appeared after the list was written.
Enumeration is a snapshot of present knowledge, while a mandatory baseline is a promise about future coverage. Those two do not compose well. Root CLAUDE.md expresses the promise without enumerating. An enumerated gate expresses the snapshot and guarantees that some future file type will be missed.
Boundary. Enumeration is correct when the intent is genuinely limited to the listed types. React component conventions scoped to /.tsx and infrastructure conventions scoped to /.tf are correct enumerations, because those conventions are not meant to govern every file type. The boundary is whether the guidance is file-type-specific or universal. Scoped conventions enumerate, universal baselines do not.
Recurring specifics. Enumerated lists that recur include ["/.py", "/.go", "/.ts", "/.tsx", "/.sql"] and ["/.tf", "/.tfvars"]. Uncovered file types that recur include Dockerfile, /.yaml, /.yml, and /.dockerfile. The phrasing that recurs is New file types keep appearing as the platform grows and neither file type was covered, so the baseline never loaded.
Proposal. keeping the baseline in .claude/rules/security.md but adding Dockerfile plus.
Why it attracts. it directly addresses the incident's file types.
Why it fails. the next incident will involve a different type, and the baseline remains gated. Moving the baseline back to root CLAUDE.md would be correct because it restores.
- Expanding
paths: ["/.py", "/.go"]to include/.tsextends coverage but keeps the structure enumerated and still gated. Moving the baseline from.claude/rules/security.mdwithpaths: ["/"]to rootCLAUDE.mdchanges from a path-matched universal simulation to a truly always-loaded baseline.
Negative globs such as `!/__generated__/` carve moving exclusions out of a type-wide rule
When a rule must follow a file type across the whole tree but must not apply inside a set of directories that moves over time, a negative pattern excludes that set while the positive pattern keeps whole-tree coverage. The syntax that recurs uses ! as a negation prefix, such as !/__generated__/ and !vendor/ alongside /*.ts.
Example for authored TypeScript that must not contaminate generated or vendored code:
Editing src/api/handlers/orders.ts matches /.ts and does not match either exclusion, so the rule loads. Editing packages/core/__generated__/client.ts matches /.ts but also matches !/__generated__/, so the exclusions apply. Editing vendor/sdk/client.ts matches !vendor/ and is similarly excluded. New __generated__/ directories that appear after schema changes are excluded automatically without updating a manual directory allowlist.
---
paths: ["**/*.ts", "**/*.tsx", "!**/__generated__/**", "!vendor/**"]
---
# Authored TypeScript Conventions
- Explicit return types on all exported functions
- No default exports
- Exhaustive `switch` handling
- Do not rewrite generated or vendored files during routine editsGenerated directories and vendored copies are tool-managed and should not be rewritten to match authored conventions, because the next codegen run reverts the changes and produces churn. The excluded set keeps moving as schemas change, so an enumerated positive list of allowed directories such as src/*/.ts would under-match future authored locations. Letting the rule follow type and carving out the moving exclusions matches the actual intent: govern authored files everywhere, govern generated and vendored files nowhere.
Boundary. If the authored set is stable and strictly rooted under a few known directories, a positive subtree scope such as src/*/.ts is simpler and avoids relying on negation syntax. The boundary is stability of the inclusion versus stability of the exclusion. When inclusions are stable and exclusions are small and fixed, enumerate inclusions. When type coverage must be broad and exclusions move, use a type glob plus negation.
Recurring specifics. Negative patterns that recur include !/__generated__/ and !vendor/. Positive type patterns that recur include /.ts and /.tsx. Generated locations that recur include __generated__/ folders and vendor/ copies. The symptom that recurs is edits to generated files causing noisy diffs and CI churn reverted by the next codegen run.
Proposal. keeping conventions in root CLAUDE.md with a prose Exclusions section listing every current __generated__/ and vendor/ path.
Why it attracts. wording changes are cheap.
Why it fails. prose still leaves the conventions always loaded for generated files and the section still depends on probabilistic application, while the directory list still requires manual updates.
- Replacing
paths: ["src//.ts", "src//.tsx"]withpaths: ["/.ts", "/.tsx", "!/__generated__/", "!vendor/"]widens from package-rootsrc/trees to the whole repo while still protecting generated and vendored locations. Removing the negation entries and keeping only the type globs widens to govern generated and vendored files and reintroduces churn.
Directory-bound globs fail when the file type keeps appearing in new locations
A directory-bound set such as paths: ["modules/", "envs/staging/", "services/billing/infra/"] matches only files under those enumerated prefixes. When the same file type appears under a new sibling, such as services/payments/infra/main.tf or experiments/infra/main.tf, the new location is ungoverned until the glob list is updated.
Contrast with a type glob:
This single pattern matches modules/networking/main.tf, envs/staging/main.tf, services/billing/infra/main.tf, and any future services/payments/infra/main.tf alike. The directory-bound version requires quarterly updates as the infrastructure layout grows, while the type version does not.
---
paths: ["**/*.tf"]
---
# Terraform Conventions
- Pin provider versions
- Prefix resource names
- Every resource must have `tags` with `environment`Directory enumeration tracks present layout, not type identity. When the governance intent is that every Terraform file should follow the same naming and tagging convention regardless of where it is added, the pattern must follow type identity rather than an evolving directory list. the tested material notes that new infrastructure folders are created most quarters and that tightening the header wording in root CLAUDE.md reduced misapplication but did not stop unconditional loading, which is a different symptom of binding to the wrong mechanism.
Boundary. Directory-bound globs are correct when the intent is to confine a convention to a known subtree, such as React Native conventions that must apply only to files under src/mobile/ and never to web components elsewhere. There the prefix src/mobile/*/ or a directory-level src/mobile/CLAUDE.md expresses that confinement more precisely than a file-type glob.
Recurring specifics. Directory-bound lists that recur include ["modules/", "envs/staging/", "services/billing/infra/"] and ["db/", "analytics/", "experiments//queries/"]. Type globs that recur include /.tf and /*.py. The growth phrase that recurs is new infrastructure folders are created most quarters.
Proposal. creating a directory-level CLAUDE.md inside each folder that currently holds Terraform files.
Why it attracts. it provides subtree confinement without a shared rule file.
Why it fails. ry-bound globs fail: every new infrastructure folder requires a new CLAUDE.md and a duplicated copy of the conventions.
When it would be right. the number of.
- Switching
paths: ["/.tf"]topaths: ["modules/", "envs/staging/"]narrows from every Terraform file to only those under known prefixes and introduces a maintenance tail. Switchingpaths: ["src/mobile//"]topaths: ["*/.tsx"]widens from one platform directory to every TSX file and contaminates web components outsidesrc/mobile/.
Project-level configuration is shared through version control, user-level is personal and does not travel
Project-level CLAUDE.md at the repository root or at .claude/CLAUDE.md and every file under .claude/rules/ and .claude/skills/ is committed into git and therefore clones with the repository. User-level ~/.claude/CLAUDE.md and ~/.claude.json live in the home directory and are never committed. A convention written only at user level is visible to that one developer on that one machine and invisible to every other contributor.
The symptom that recurs is a new engineer who clones the same repository on the same branch but sees inconsistent conventions. Investigation shows the outlier has no local user-level copy while the senior developer's sessions follow standards that exist only in ~/.claude/CLAUDE.md. The fix in every evidence item is the same: move the team standard from ~/.claude/CLAUDE.md into a project-level file committed to the repository.
Tree that shows the sharing boundary:
repo/ # committed, clones with the repo
CLAUDE.md
.claude/
rules/
python-style.md # paths: ["**/*.py"]
terraform.md # paths: ["**/*.tf"]
~/.claude/ # personal, never committed
CLAUDE.md # personal preferences: 2-space indent, aliases
CLAUDE.mdSharing is a location property, not a content property. The same Markdown text placed at user level and at project level has different reach. Team-wide secure-coding standards, input validation rules, and PR templates that must apply identically across thirty separate service repositories must live at project level in each repository, either directly or via a shared file referenced by @import, so that every clone resolves one current version without per-person setup.
Boundary. User-level placement is correct for personal preferences that should apply across all of that developer's projects, such as 2-space indentation, trailing commas, or strict TypeScript preferences that differ from the team. The boundary is audience. If the rule must reach every contributor who clones the repository, it must be project-level. If it must reach every project that one person touches, it can be user-level.
Recurring specifics. Paths that recur include ~/.claude/CLAUDE.md, ~/.claude.json, ~/.claude/CLAUDE.md versus CLAUDE.md versus .claude/CLAUDE.md, and .claude/rules/ as the committed alternative. The diagnostic that recurs is /memory showing loaded memory files and revealing that the new hire's session has no project-level rule loaded. The onboarding failure that recurs is roughly 40 percent of first-week PRs ignoring conventions when standards lived only at user level.
Proposal. keeping standards in the senior developer's ~/.claude/CLAUDE.md and asking each contractor to copy that file during onboarding.
Why it attracts. it preserves the senior's working setup.
Why it fails. adoption is inconsistent, some newcomers skip the step, and every future hire repeats the same manual copy.
When it would be right. the preferences are personal and.
- Moving
~/.claude/CLAUDE.mdtext into project-levelCLAUDE.mdcommitted at the repo root changes from personal reach to team reach. Moving it instead into.claude/rules/coding-standards.mdwithpaths: ["/.c", "/.h"]also moves to team reach but adds a file-type gate.
File-type automatic conventions call for `.claude/rules/` while task-triggered workflows call for Skills
.claude/rules/ files with paths frontmatter apply automatically when the file being edited matches the gate. They stay in context as background guidance for that edit and are invisible otherwise. Skills at .claude/skills/<name>/SKILL.md are invoked by deliberate action, either an explicit slash command such as /review or /deploy or an intent match such as repairing data during an outage. They load only when the task is underway, not when a file of a certain type is opened.
Evidence states the exam trap explicitly: both Skills and .claude/rules/ can carry path-style frontmatter, but they serve different purposes. Rules shape every edit to a matching file. Skills run task-style workflows triggered by what the engineer is trying to do.
The trigger must match the intent. A testing convention that must govern every .test. file should not depend on the developer remembering to run a command before writing tests, because forgetting defeats the purpose. A deployment procedure that should run only when the team is deploying should not occupy context during routine feature work, because it bloats every session. Automatic file-type guidance and on-demand task workflows map to different mechanisms.
Boundary. The boundary is action versus artifact. If the guidance is about how to write a file of a given type, such as snapshot bans and accessibility assertions for */.test.tsx, use a path-scoped rule. If the guidance is about a procedure that happens occasionally and is not tied to a file type, such as incident response, database migration replay ordering, or PR review checklists, use a Skill. the tested material marks the reverse choice as incorrect: placing file-type test conventions in a Skill requires invocation before every test edit, and placing a runbook in a path-scoped rule loads it for any file under a matching path rather than only during the intended situation.
Recurring specifics. Task examples that recur include PR review, deployment, database migration, cold-store recovery, and incident response. File-type examples that recur include /.test., /.tf, /.py, and /*.tsx. The phrase that recurs is automatic, always-on convention loading for a file type versus task-specific workflows invoked on demand.
Proposal. a Skill that is invoked before editing migration files scattered across src/users/migrations/, src/billing/migrations/, and src/inventory/migrations/.
Why it attracts. Skills feel like the natural place for checklists.
Why it fails. the convention must apply automatically whenever any migration file is generated or edited, not only when the developer remembers to.
- Converting
CLAUDE.mdalways-loaded sections about PR, deploy, and migration into Skills while keeping coding and testing standards inCLAUDE.mdchanges from always-loaded to on-demand for workflow guidance. Moving the same migration checklist from a Skill into.claude/rules/withpaths: ["/migrations//*"]changes from intent-triggered to file-triggered.
Verbose situation-triggered runbooks should be Skills with `context: fork` rather than path-scoped rules
A runbook that is needed only during an incident, such as data-recovery rehydration after a storage tier expires or cold-store replay ordering and checkpoint recovery, is not tied to editing a particular file type. It is triggered by a storage alert or an engineer reporting that an incident is in progress. The correct home is .claude/skills/<name>/SKILL.md set up as an on-demand skill.
Frontmatter that recurs for isolating verbose execution:
The context: fork field directs the runbook's verbose step-by-step execution into an isolated sub-agent context rather than crowding the engineer's main working session. Without it the skill still keeps the runbook out of routine sessions, but its output during execution still fills the main conversation. With it both problems are fixed: the guidance does not load during ordinary development, and when it does run its diagnostic output does not pollute the working conversation.
---
name: incident-response
context: fork
---
# Incident Response Runbook
- Replay ordering, checkpoint recovery, backfill validation
- Multi-step diagnostic output is expected and verbosePath-scoped rules cannot solve this because the trigger is intent rather than file path. A rule with paths: ["/*"] would load for any edited file and still crowd routine sessions. A directory-scoped rule with paths: ["infra/", "deploy/", "runbooks/"] still loads when an engineer edits infrastructure or deployment files for reasons unrelated to the incident. A prose header in root CLAUDE.md that says consult this section only during outages still occupies context during routine work. Only an on-demand mechanism with isolated execution solves both the loading cost and the output pollution.
Boundary. A Skill without context: fork is the nearby case where the runbook is short and non-verbose. the tested material distinguishes context: fork for verbose multi-step diagnostic output that would otherwise crowd the conversation from plain invocation for concise guidance. If the procedure is always short, the isolated context is less important but the Skill placement remains correct over path-scoped rules or root CLAUDE.md.
Recurring specifics. Trigger phrasing that recurs includes when those tasks are underway, by situation, after a storage alert fires, and not tied to any particular source files or directories. Runbook topics that recur include replay ordering, checkpoint recovery, backfill validation, and rehydrating archived records after a storage tier expires. The frontmatter field that recurs is context: fork for isolated execution.
Proposal. moving the 1,800 line incident-response runbook into .claude/rules/incident-response.md with paths: [".
Why it attracts. path-scoped rules also promise conditional loading.
Why it fails. the trigger is not a file path, so the gating misaligns with when the runbook is actually needed, and the broad */.
- Moving the runbook from root
CLAUDE.mdinto a Skill withcontext: forkremoves it from routine context and isolates its output. Changing the Skill to omitcontext: forkkeeps it out of routine context but lets verbose output fill the main session.
Overlapping globs require clarity on precedence and the cost of double-loading
When two rule files have overlapping paths arrays, any file whose path matches both will load both rule bodies. A file at src/api/auth.test.ts matches both a type glob /.test.ts and a subtree glob src/api//. A file at ios/Features/View.swift matches both ios/ and /*.swift. Overlap is not an error, but it has consequences.
An example pair that overlaps at the boundary:
Editing src/api/auth.ts loads only the API file. Editing src/api/auth.test.ts loads both. The token cost for that file is the sum of both rule bodies. If the two rules contain contradictory instructions, the model may pick arbitrarily, as the tested material notes for root CLAUDE.md contradictions and applies analogously to overlapping rules.
---
paths: ["**/*.test.ts", "**/*.test.tsx"]
---
# Testing Conventions
- Use `describe` and `it` with sentence-style names
- Assert behavior, not implementation---
paths: ["src/api/**/*"]
---
# API Conventions
- All endpoints return `{data, error, metadata}`
- Validate with `zod` at the handler boundaryWhole-tree type globs and subtree globs overlap at every test file inside the relevant subtree. That overlap is often intentional, but the team must be prepared for two sets of conventions to be visible at once and for their interaction to be read as additive rather than conflicting. The cost is measurable, the model must carry both rule texts while editing the overlapping file.
Boundary. A single focused file per topic with non-overlapping scopes is the simpler case. When testing conventions are already covered by */.test.*, the API rule does not also need to restate testing guidance. Overlap is benign when the two topics are complementary, such as testing plus API error handling, and problematic when they contradict, such as one requiring named exports and another implying default exports for the same extension.
Recurring specifics. Overlapping path pairs that recur include /.test. plus src/api//, /.swift plus ios/, /.ts plus src//.ts, and terraform// plus /.tf. The contradiction symptom that recurs is inconsistent application when different sections contradict each other in a bloated CLAUDE.md. Diagnostics show both rules loaded.
Proposal. overlapping rules are de-duplicated and only one loads.
Why it attracts. it assumes an optimization.
Why it fails. there is no automatic de-duplication of distinct rule files that both match, each body is loaded independently. A related but distinct fact is that enabling the same Skill in multiple places does merge and load once, which does not apply to distinct.
- Splitting a large rule into two overlapping rules increases token cost for files that match both and creates a precedence question. Narrowing one of the pair, such as
src/api/*/tosrc/api/*.ts, reduces the overlap to one level and hides deeper test files.
Keep rule granularity to one focused topic per file for ownership and review
Each .claude/rules/ file should cover one coherent topic, such as testing, Terraform, or API conventions, with a descriptive name that reflects that topic. the tested material recommends splitting a monolithic CLAUDE.md that mixes SQL style, Python lint, dbt naming, Airflow patterns, security checklists, and PR etiquette into topic-specific files such as testing.md, api-conventions.md, deployment.md, and security.md under .claude/rules/.
Layout that shows ownership:
repo/
.claude/
rules/
testing.md # owned by QA, paths: ["**/*.test.*"]
api-conventions.md # owned by API team, paths: ["src/api/**/*"]
terraform.md # owned by platform, paths: ["**/*.tf"]
frontend.md # owned by frontend, paths: ["**/*.tsx"]
migrations.md # owned by DBA, paths: ["**/migrations/**/*"]Focused files make ownership, review, and discovery tractable. A change to testing conventions appears as a diff in testing.md alone rather than as churn inside an 800 line mixed file where unrelated sections move in every pull request. Reviewers can own one file per team. Search and navigation become file-name driven rather than header-scanning within a single large document.
Boundary. Consolidation into one generated file with no section ownership is the opposite extreme that the tested material marks as a distractor. Duplicate files with identical content and ambiguous names are another distractor that creates drift and unclear authority. The boundary is coherent ownership. One topic per file with a descriptive name hits that boundary.
Recurring specifics. File-name examples that recur include testing.md, api-conventions.md, deployment.md, security.md, style.md, frontend.md, db.md, and tooling.md. The principle that recurs is Focused Markdown files under .claude/rules make large project guidance easier to own, review, and discover than a single mixed instruction document.
Proposal. putting all repository guidance into one generated file with no section ownership to keep the structure simple.
Why it attracts. it reduces file count to one.
Why it fails. a monolithic generated file is harder to maintain, increases conflict risk across teams, and forces every change to touch the same file. Focused topic files
When it would be right. ownership and review.
- Splitting
CLAUDE.mdcovering TypeScript, testing, API, and deployment into four.claude/rules/files changes from one mixed file to four owned files. Mergingtesting.mdandapi-conventions.mdback into one file reduces ownership clarity and couples two teams' reviews.
Use `@import` for modular always-loaded composition and `paths` for conditional loading
Both @import and paths frontmatter split work out of a large file, but they serve distinct intents. @import keeps the reference always loaded, paths makes it conditional.
- Split universal standards that must be present every session into standards/api.md, standards/testing.md, and similar modules and reference them with @./standards/api.md inside root CLAUDE.md. Each import is inlined at startup, so composition is achieved without changing load behavior. - Split file-type or subtree-limited standards into .claude/rules/testing.md with paths: ["/.test."] and .claude/rules/terraform.md with paths: ["/*.tf"] so each is conditionally loaded only for matching edits.
Evidence pairs the two for fixing an 800 line file: split via @ imports plus .claude/rules/ for path-specific content.
A single splitting strategy cannot solve both problems. Always-loaded sections that are split with paths would become gated and disappear for unrelated sessions where they are still required. Conditionally-loadable sections that are split with @import would remain always loaded and keep their token cost. Using each for its intended intent matches the correct load behavior to the correct content.
Boundary. If the question states that every section genuinely applies to every session and the team does not want conditional loading, @import alone is correct. If the question states that any topic only matters when working in the matching part of the codebase, .claude/rules/ is correct. The boundary is the stated loading requirement.
Recurring specifics. Import examples that recur include @./standards/api.md, @./standards/testing.md, and @./standards/naming.md. Path examples that recur include paths: ["api/"] and paths: ["/.test."]. The phrase that recurs is @path imports load eagerly and the imported file's content is inlined.
Proposal. splitting every topic into README.md files in the relevant subdirectories and relying on automatic loading of README.md as instructions.
Why it attracts. README.md is already present and familiar.
Why it fails. README.md is not a mechanism for automatic instruction loading, and multiple such files would need synchronization that @import plus .claude/rules/.
- Adding
@importto an 800 line root file without moving any section into.claude/rules/improves maintainability but keeps token cost identical. Moving the testing section from an imported module into.claude/rules/testing.mdwithpaths: ["*/.test.ts"]changes that section from always-loaded to conditional.
Verify any path rule by comparing its globs against real repository paths and session diagnostics
A path rule can be written correctly in Markdown and still fail to govern the intended files because the glob string does not match the actual repository layout. The verification workflow that the tested material recommends is to check the frontmatter, validate glob syntax, compare the strings against real file paths, and run the session diagnostics /context or /memory while editing a representative file.
Example verification steps for a developer portal rule intended for Markdown files:
- Confirm that the rule at .claude/rules/developer-portal.md contains paths: ["*/.md"] in frontmatter, not only in the body. - Confirm that */.md actually matches the repository layout, such as docs/portal/guide.md and portal/README.md, and does not require a more specific prefix. - Edit a matching file in the intended location and run /context to verify that developer-portal.md appears as loaded, then edit a non-matching file in another area and verify it does not appear.
Globs are string comparisons against paths, not intentions. A pattern such as terraform// that assumes infrastructure lives under terraform/ fails when the team actually stores Terraform under infra/terraform/ and modules/. A pattern such as src/api// that assumes a rooted prefix fails when services store API code under services/billing/api/. Verification against real paths catches the mismatch before it is relied upon in review automation.
Boundary. The opposite approach that the tested material marks as distractors is adding unrelated prose to the rule until the model notices it, assuming pattern matching ignores separators or case across platforms, or moving source files to satisfy a mistaken glob. The correct boundary is to fix the pattern, not the repository layout or the prose around it.
Recurring specifics. Diagnostic commands that recur include /context and /memory. Instructions that recur include verify the glob matches the repository layout and compare the release workflow paths pattern with real file paths and correct the rule before changing its substantive instructions. The field that recurs is paths frontmatter, not body prose.
Proposal. assuming case insensitivity and separator agnosticism across environments and therefore skipping verification.
Why it attracts. it trusts a convenient assumption.
Why it fails. pattern behavior must be checked against actual documented syntax and platform paths. Verification against real file paths would be correct.
- Changing
paths: ["terraform//"]topaths: ["infra/terraform//"]fixes a mismatch when Terraform is actually underinfra/terraform/. Changingpaths: ["/.md"]topaths: ["docs/portal//.md"]narrows from every Markdown file to only portal documentation and would drop other Markdown files intentionally.
`.mdc` style fields such as `alwaysApply`, `description`, and `globs` mirror the same always versus gated choice
The .mdc style of rule files uses different field names for the same trade-off. The pattern that recurs includes:
Setting alwaysApply: true makes the rule load for every session regardless of patterns, which corresponds to placing the guidance in root CLAUDE.md. Setting alwaysApply: false combined with globs makes loading gated by path match, which corresponds to paths frontmatter inside .claude/rules/. The description field is used for model-side selection when alwaysApply is not set, providing an intent-based trigger that is still probabilistic rather than structural.
Flag uncertain official syntax explicitly: the tested material shows both globs and paths as the frontmatter path array field, and both alwaysApply as a boolean and alwaysApply: false with globs as a pair. The canonical documented field for Claude Code in the reference material is paths inside .claude/rules/. Treat globs as a marketplace style spelling and verify against the official documentation when authoring real files, rather than assuming globs is the canonical name in this repository's tooling. See docs/last-step-scratchpad/MEMORY.md for the instruction to cite only Anthropic URLs verified to resolve and to mark inferred details.
---
alwaysApply: false
description: "Terraform infrastructure conventions"
globs: ["**/*.tf", "terraform/**/*"]
---
# Terraform Conventions
- Use `snake_case` for resource names
- Tag every resource with `environment` and `team`The same tension between universal and conditional applies under any field vocabulary. An always-on baseline should not be written as gated with globs simply because the file format offers a globs field. A file-type convention should not be written as alwaysApply: true simply because that is the file's default. The choice of fields should mirror the actual breadth of applicability, not the convenience of the template.
Boundary. A security baseline placed behind globs: ["/.py", "/.go", "/.ts"] with alwaysApply: false is the gated universal anti-pattern under this vocabulary, just as paths: ["/.py", "*/.go"] is the equivalent anti-pattern under Claude Code's vocabulary. The correct opposite is alwaysApply: true with no globs and with the baseline in an always-loaded location.
Recurring specifics. Fields that recur include alwaysApply, description, and globs as a sibling to paths. Phrases that recur include always-apply and description-based selection. The token-efficiency argument that alwaysApply: true loads for every session while globs loads conditionally is the same as root versus path-scoped.
Proposal. globs: [".
Why it attracts. it uses the available path mechanism to mimic always-on. It fails for the same reason paths: ["*/"] as a simulation
Why it fails. it is still a path match rather than an explicit always-on declaration. Setting alwaysApply: true and removing globs
When it would be right. universal guidance.
- Switching
alwaysApply: truetoalwaysApply: falseplusglobs: ["/.tf"]changes from always-on to file-type gated. Switchingpaths: ["/.tf"]toglobs: ["*/.tf"]changes only the field spelling while preserving the gating intent, subject to tooling's canonical name.
`Glob` enumerates by path, `Grep` searches inside contents, `Read` loads a single file
Glob takes a pattern such as /.tf or config//.json and returns the list of file paths whose names match that pattern, without reading any file content. Grep takes a string or regex such as fact_revenue_daily or import.*@company/auth and searches inside file contents for that sequence. Read takes one explicit file path such as src/api/auth.ts and loads that file's content into context.
The two search tools are complementary. To locate every file that references the table fact_revenue_daily and then list related migration files, the correct sequence is Grep for fact_revenue_daily to find content references, followed by Glob for /migrations/.sql to enumerate migration files by path. Using Glob for /fact_revenue_daily* misses content that is not reflected in filenames, while using Grep for both tasks would find migration files that happen to contain the table name but would not enumerate every migration file.
Path enumeration and content search answer different questions. Type-scoped rules govern files by what they are called, so Glob with */.test.ts is the natural analog for discovering test files across dozens of directories. Locating where a function such as createInvoice is called or where a table is referenced requires searching inside contents, so Grep is the analog there. Reading every file upfront to answer either question exhausts context and defeats the token-efficiency goal that path-scoped rules serve.
Boundary. The opposite case where Grep is always the first step is large unknown codebase exploration with a discount bug spanning 1,400 files. There Grep for discount-related symbols finds the entry point, then offset Read plus import tracing follows the call chain into dependent modules. Enumerating every file under checkout/ and reading each in full would front-load the context window with unrelated code and cause the model to lose earlier files.
Recurring specifics. Patterns that recur include Glob with /.test.ts, /.spec.ts, /migrations//.sql, frontend//.test.tsx, and config//*.json. Grep patterns that recur include fact_revenue_daily, @company/auth, createInvoice, and formatDate. The tool sequence that recurs is Grep then Glob or Grep then offset Read.
Proposal. Grep for *.test.ts to find test files by name.
Why it attracts. Grep is the more familiar search tool.
Why it fails. Grep searches contents, not path names. Glob with */.test.ts
When it would be right. path enumeration.
- Replacing
GlobwithGrepfor*/.test.tsswitches from path match to content search and produces false positives and negatives. ReplacingGrepwithGlobforfact_revenue_dailyswitches from content search to filename search and misses nearly every real reference.
When a team is strictly subtree-confined, directory-level `CLAUDE.md` is the tighter fit
A team whose files for one platform never leave a rooted directory is better served by a directory-level CLAUDE.md inside that directory than by a type glob that matches by extension anywhere. the tested material explicitly marks this as correct for ios/ plus android/ plus backend/ where Swift, Kotlin, and Python helpers stay inside their own root and new files are added there rather than under new top-level roots.
Placement for the confined case:
Each platform CLAUDE.md loads only when work is inside that subtree, which matches the architectural intent that platform conventions should never govern files outside their root. The type-glob alternative paths: ["*/.swift"] would apply Swift conventions to any Swift file that appeared elsewhere, which the architecture says should not exist.
repo/
CLAUDE.md # universal release standards
ios/
CLAUDE.md # Swift access control and SwiftUI patterns
Features/
PayView.swift
android/
CLAUDE.md # Kotlin coroutine and null safety
feature/
PayView.kt
backend/
CLAUDE.md # Python API versioning
services/
pay.pySubtree confinement is a stronger invariant than file-type presence. When the invariant is real, expressing it directly with a subtree scope prevents over-matching and communicates intent more clearly than a diffuse type glob. It also avoids loading Swift rules during backend-only work even when a stray Swift file appears, because that stray file would be under backend/ rather than ios/.
Boundary. When matching files are a type scattered across many feature directories, directory-level files become the wrong axis. Tests beside sources across 40 directories, migration files under services/billing/migrations/ plus services/auth/migrations/, and infrastructure files under modules/ plus envs/ are that opposite case. There a type glob such as /.test.tsx or /.tf is correct and a directory-bound approach creates duplication.
Recurring specifics. Subtrees that recur include ios/, android/, backend/, src/mobile/, and services/*/api/. The phrase that recurs is conventions locked to a directory subtree call for directory-level CLAUDE.md and the diagnostic that type globs are for same-type files spread across the whole codebase.
Proposal. file-type globs.
Why it fails. file-type globs are the dominant success pattern elsewhere. It fails as the tighter expression of confinement, because the globs still describe type rather than architectural containment, and the tested material marks directory-level files as the intended answer for this.
- Changing
ios/CLAUDE.mdtopaths: ["/*.swift"]widens from subtree to whole tree by type. Changingpaths: ["ios/"]toios/CLAUDE.mdmoves from a centralized conditional rule to a co-located subtree file while preserving containment.
A bloated root `CLAUDE.md` is fixed by splitting into focused topic files, not by trimming or rewording in place
When root CLAUDE.md has grown to 800 plus lines mixing API design, testing conventions, deployment procedures, security policies, and style guides, developers report that relevant guidelines are missed and contradictions between sections appear. The fix that recurs in the tested material is to split by topic into separate files, either as @import modules for always-loaded universal content or as .claude/rules/ files with paths gates for conditional content, preserving every standard verbatim in exactly one file.
Example split:
The root becomes a short navigable index that still auto-loads universal modules, while path-scoped rules load only when their matching files are edited. No standard is dropped.
repo/
CLAUDE.md # short index plus @imports for universal standards
.claude/
rules/
testing.md # paths: ["**/*.test.*"]
api-conventions.md # paths: ["src/api/**/*"]
terraform.md # paths: ["**/*.tf"]
style.md # always-loaded via @import if universalRewording headers, adding a table of contents, or pruning low-traffic topics treats the symptom rather than the load behavior. Contradictions arise because changes to one topic risk disturbing adjacent unrelated sections in the same file. Focused files give each topic its own review surface, reduce per-session tokens for conditional topics, and keep the model focused on conventions that actually apply to the current work.
Boundary. The nearby case where splitting with a gate is incorrect is the 900 line file where all standards are still required by audits and where none can simply be dropped. Pruning lowest-traffic standards to keep the imported set small, and folding minor topics into a shared misc-standards.md, is marked as incorrect because it reduces coverage. The correct fix there is to preserve every standard in exactly one file with the appropriate load behavior, not to cut content.
Recurring specifics. Line counts that recur include 600, 650, 800, 850, and 900 lines for the bloated file. The refactoring phrasing that recurs includes Split into focused topic files in .claude/rules/ and Split into modular files using @ imports. The symptom that recurs is attention dilution and difficulty finding the transaction rollback rule.
Proposal. restructuring the single file under clearer headers and a table of contents while keeping the same always-loaded size.
Why it attracts. it preserves the single-file workflow.
Why it fails. headers do not change token cost and the large flat file still mixes unrelated sections, so cross-topic bleed and navigation pain remain.
- Splitting
CLAUDE.mdvia@importwithout adding anypathsgates keeps every section always loaded and improves only maintainability. Moving topic-scoped sections into.claude/rules/withpaths: ["api/"]changes from always-loaded to conditional and reduces tokens for unrelated sessions.
A canonical shared standard must be referenced as a single source, not copied into each location
When the same secure-coding standard must apply identically across roughly 30 separate service repositories, copying the text into each repository's root CLAUDE.md at creation time produces drift. The standard was revised four times in one year, but audits found repos enforcing superseded PII logging and conflicting validation rules because updates still depended on each squad editing its own copy. A CI job that syncs the block from a shared repo into each CLAUDE.md on every merge automates the copy but still leaves the mechanism as copy rather than reference.
The durable fix is to publish one canonical file such as secure-coding-standards.md in a shared location vendored into every repository and to replace the inline copy in each repository's root CLAUDE.md with an @import of that single file.
Every session then resolves the one current document at load time, with no per-squad re-sync.
# CLAUDE.md
- Project notes and architecture decisions
@./shared/secure-coding-standards.mdReference eliminates the coordination tail that copy requires. Every edit to the canonical file takes effect on the next session without asking 30 squads to re-paste. Drift is structurally prevented rather than monitored after the fact with audits and emails. The distinction is between copying standards and referencing a single current version.
Boundary. The nearby case where copy-like behavior is acceptable is @import plus vendoring, which is copy in the sense that a file is pulled into the repository, but reference in the sense that the directive resolves to one current file. Pure automation that syncs inline text into CLAUDE.md on merge still counts as copy, because the authoritative source remains the synced inline text rather than the imported file.
Recurring specifics. Numbers that recur include 30 separate service repositories, four revisions in one year, company-wide secure-coding standards, input validation, secrets handling, and PII logging. The phrase that recurs is one canonical source via @import and rather than copy.
Proposal. keeping the inline standards and replacing them with a clearly delimited SECURE-CODING STANDARDS section synced by a CI job on every merge.
Why it attracts. it automates the update and feels like a technical fix.
Why it fails. the mechanism remains copy into each CLAUDE.md rather than reference to a single file. the tested material marks this as insufficient.
- Replacing inline standards in each repository's
CLAUDE.mdwith@./shared/secure-coding-standards.mdconverts from 30 copies to one reference. Replacing the reference with a CI sync that rewrites the inline block preserves copy as the mechanism. Moving the canonical file into.claude/rules/standards.mdwith a path gate reintroduces holes for non-matching types.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| .claude/rules with paths | Directory level CLAUDE.md | Rules with paths apply to a glob across the entire codebase. Directory level applies only to files in that single directory. Co located test files across many directories are the canonical rule case. |
| .claude/rules with paths | Root CLAUDE.md | Rules load only when a matching file is read. Root loads on every session. Conditional rules are the token efficient choice for file type specific conventions. |
| .claude/rules with paths | Skills SKILL.md with paths | Both can conditionally activate on paths, but rules stay in context as background guidance for matching files while skills load as on demand workflows triggered by intent or paths. Always on convention loading for a file type is a rule. |
| star star slash star dot test dot ts | src slash star star | File type glob matches test files anywhere regardless of directory. Directory glob matches every file under a fixed subtree. Choose by whether the convention follows extension or directory. |
| claudeMdExcludes | Allow ask deny permissions | claudeMdExcludes skips specific CLAUDE.md files from discovery. Allow ask deny governs tool permissions. They solve different problems and are tested as a skips files versus governs tools pair. |
Traps
The tempting answer. Cover test files co located with source across many directories by adding CLAUDE.md to each directory that contains tests.
Why it fails. That requires one file per directory and a new file for every new test directory, with drift as copies fall behind. Path scoped rules cover the same set with one file and one glob such as star star slash star dot test dot ts.
What is correct. Place the conventions in .claude/rules/testing.md with paths globs for test extensions and verify via /context that the rules appear only when a test file is edited.
The tempting answer. Put Terraform conventions in root CLAUDE.md because root is the simplest shared location.
Why it fails. Root loads on every session even while editing a React component or an API handler, burning tokens on irrelevant guidance and degrading attention. The exam tests the efficiency loss and expects path scoped rules as the fix.
What is correct. Move file type specific conventions to path scoped rules with targeted globs so they load only when a matching file is read.
The tempting answer. Use a skill with paths frontmatter to load test conventions because skills can also auto activate conditionally.
Why it fails. Skills load as on demand workflows, not as always on background guidance that shapes every edit. Rules stay in context as guidance once loaded, which is the behavior the question about automatic convention loading expects.
What is correct. Use path scoped rules in .claude/rules for automatic always on guidance tied to a file pattern, reserve skills for task workflows.
The tempting answer. Rely on lazy loading to hide another team's CLAUDE.md in a monorepo with many nested memory files.
Why it fails. Walk up and lazy discovery still surface files across the tree, so irrelevant guidance keeps appearing. The mechanism designed to skip them explicitly is claudeMdExcludes as globs against CLAUDE.md file paths.
What is correct. Add an exclude pattern such as star star slash experimental slash star star to skip the unwanted memory files rather than asking Claude to ignore already loaded content.
The tempting answer. Use paths terraform slash star star for Terraform standards because the primary directory is named terraform.
Why it fails. Terraform files are identified by extension star dot tf and can appear outside terraform slash in a monorepo. A file type glob such as star star slash star dot tf covers them everywhere while a directory glob misses non standard locations.
What is correct. Choose file type globs when the convention follows extension, directory globs when it follows directory scope, matching the precision the convention actually requires.
Plan Mode and Execution Control
Choose plan upfront when ambiguity is stated, execute directly when scope is clear, and use the hybrid for multi file migrations while keeping discovery isolated.
What you need to know
Claude Code operates in two main modes whose choice is governed by ambiguity, not difficulty. Plan mode is for complex work where the developer must explore the codebase, evaluate multiple approaches, and design a strategy before changing code. Direct execution is for well understood changes with clear limited scope where the correct approach is already known. The exam consistently applies this rule: if the task description leaves room for design choices, plan mode is correct even when the change sounds small, and if the description is specific enough that one obvious implementation exists, direct execution is correct even when the bug is tricky.
Plan mode fits the enumerated ambiguous set: large scale changes such as monolith to microservices restructuring or module reorganisation, cases where multiple valid approaches exist with different infrastructure requirements, architectural decisions about service boundaries or contracts with downstream cost, multi file modifications such as library migrations affecting dozens of files that require a consistent strategy, and codebase exploration tasks that need dependency or data flow mapping before any change. Direct execution fits the complementary clear scope set: single file bug fixes with a clear stack trace, validation conditionals added to one function, configuration value updates, and any well defined task where the developer knows what to change and how, with no design decision to make.
The hybrid the exam repeatedly tests is plan then execute. Use plan to explore, understand dependencies, evaluate approaches, and design the migration pattern, then switch to direct execution to apply the planned approach file by file with the strategy already decided. A library migration across 30 files is the canonical example, the plan phase finds every importer of the old library, maps API differences, designs the per file pattern, and checks edge cases, while the execute phase applies that pattern consistently so no file diverges from the design.
The Explore subagent is the isolation mechanism that keeps plan phase discovery from polluting the main conversation. Multi phase exploration produces file listings, dependency graphs, code excerpts, and analysis notes, and letting all of that flow into the main window fills context and degrades snippet quality for the implementation step. The Explore subagent runs discovery in its own isolated context window, returns a concise summary to the main session, and keeps the main window clean for the actual change. The verb that pairs with Explore is subagent delegation and the primary benefit is context isolation, not parallelisation.
The most common exam fault is delaying plan mode until complexity shows up. When requirements already state complexity such as restructure the monolith into microservices, choose plan immediately because the complexity is stated, not hidden. Starting direct and switching only when the first surprise appears incurs rework that upfront planning would have avoided. Conversely, a single function fix with a known cause and a clear stack trace is direct execution, and planning there adds overhead without benefit. The discriminator is stated ambiguity at arrival, not emergent difficulty during execution.
How plan mode behaves and how it switches
Plan mode is invoked with /plan and enforces read only exploration, Claude reads files, traces dependencies and data flow, analyses tradeoffs between approaches, and proposes a structured plan covering identified dependencies, evaluated approaches with tradeoffs, and a recommended implementation strategy, without modifying any files. It is not a separate model or system prompt but a behavior constraint layered on the same runtime.
Switching to execution is a developer prompt flow, exiting plan mode and proceeding with implementation, there is no compile time transition, and in headless print mode plan semantics are reached via --permission-mode plan which restricts the session to read only operations for generating reports or plans without touching the repo. The interactive and headless forms share the same read only intent but differ in iteration support.
Explore subagent and the pre ship trio
The Explore subagent is one of the bundled subagents Claude Code ships, invoked through the Task or Agent tool with a description and exploration prompt. It operates with its own context window, runs the discovery, and returns a structured summary as a tool_result block to the main conversation, mirroring any subagent delegation shape while preserving isolation as the primary benefit.
Plan mode composes with the pre ship workflow of /diff plus /code-review. /diff shows what changed in the working directory, /code-review is a bundled skill that reviews the diff and can apply findings via --fix with /code-review ultra for cloud multi agent review, and /security-review is the deeper security pass. Plan is pre change design, diff plus review is pre ship verification, they are complementary not interchangeable.
Criteria the exam keys
Plan mode signals in a stem are phrases such as multiple valid approaches, evaluate tradeoffs, service boundaries, module reorganisation, library migration across many files, or understand existing structure. Ambiguity is the trigger, not line count, and the presence of several plausible integration architectures is enough to select plan immediately rather than waiting for difficulty to emerge.
Direct execution signals are a clear stack trace with a known cause and a single function to change, a configuration value update, or a well defined validation conditional. Scope is the trigger, known location and known approach with limited blast radius, which the exam frames as the case where planning would add overhead without changing the design.
Composition with parallel and background work
Plan mode can fan out to multiple Explore subagents for parallel investigation of different subsystems, then sequence through /batch style decomposition where a large change is split into independent units in separate git worktrees. /agents opens the subagent manager, /tasks lists background work, /background detaches a session while freeing the terminal, and /btw adds a quick aside without bloating main history.
In print mode the composition is more constrained, headless plan is useful for risk reports and plans in CI without edits, but it cannot replace iterative interactive plan mode for complex design where exploration must refine with human feedback. The exam presents which mode for a CI planning step as --permission-mode plan with -p for headless planning.
Authoritative mechanism reference
The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.
Mechanism reference: The six documented permission modes
Claude Code exposes a single permissionMode setting that decides which actions run without a permission prompt. The documentation lists six values, each a different tradeoff between convenience and oversight:
These six values sit on top of the agent loop rather than replacing it. In every mode the loop still perceives, plans, chooses a tool, executes it, and observes the result; what changes is which tool calls require a permission prompt before they run. Reads, searches, and reasoning are free in every mode because they cannot mutate state, so the agent can always explore. The mode decides only where the write boundary sits: at plan it sits before any edit; at default it sits before each non-read action; at acceptEdits it sits after file edits but before most shell commands; at dontAsk it sits after the pre-approved set; at bypassPermissions it is removed entirely. Understanding the mode as a movable write boundary, not a different agent, is the cleanest mental model for the exam.
default- Reads only run without a prompt. Every edit, write, or shell command that is not already covered by an allow rule prompts the user. In the CLI, VS Code, JetBrains, and Desktop surfaces this is labelled Manual. The config value isdefault, and the CLI accepts themanualalias (themanualalias requires Claude Code v2.1.200 or later).acceptEdits- Reads, file edits, and common filesystem commands such asmkdir,touch,mv,cprun without a prompt. Other shell commands and network requests still need an allow rule or prompt.plan- Reads run, and the session blocks edits and writes until you review and approve a plan. When auto mode is available, classifier-approved commands may also run during planning.auto- A second model, the classifier, reviews most actions instead of you. Available on Pro, Max, and Team plans and on supported models; an organization can turn it off.dontAsk- Only pre-approved tools run; everything else is auto-denied. Best for locked-down CI and scripts. This mode never appears in the interactiveShift+Tabcycle; you set it with--permission-mode dontAsk.bypassPermissions- Prompts and safety checks are disabled, so tool calls execute immediately. Reserved for isolated containers, VMs, or sandbox runtimes. It can be blocked organization-wide viapermissions.disableBypassPermissionsMode.
The reference page centres on two of these, plan and default (direct execution), but the exam can reference the full value space, and a candidate should know that acceptEdits is the closest "just let it edit" mode, dontAsk is the CI lock-down mode, and bypassPermissions is the container-only escape hatch.
Mechanism reference: How planning is entered
There are three documented ways to reach plan mode:
- CLI flag at launch:
claude --permission-mode plan "<prompt>". This sets the starting mode for the whole session. - Live key chord in an interactive session: press
Shift+Tabto cycle through the modes. Fromdefault, the first press goes toacceptEditsand the second press goes toplan; the cycle then returns todefault. On Windows when the Node or Bun runtime does not enable VT input mode,Alt+Mperforms the same cycle. - IDE or Desktop control: the mode indicator in VS Code or the mode selector in the Desktop app. Asking Claude in chat to change the mode does not work; you must use the control.
The reference page also shows claude --plan as a shorthand and /plan as a slash command. The current CLI reference documents only --permission-mode plan, not a --plan shorthand, and does not list a /plan slash command. Treat claude --plan and /plan as not independently confirmed until confirmed against shipping docs; the writer should prefer the documented --permission-mode plan form and the Shift+Tab cycle.
Mechanism reference: How planning is left (the approval step)
Plan mode blocks writes. To execute the plan, the human moves the session out of plan by switching the permission mode: press Shift+Tab again, use the mode indicator or selector, run /permissions, or start a separate writable session. The reference page frames this as /approve "moving out of plan mode and starting to execute", and as /plan (while already in plan mode) "surfacing the proposed diff-like changes for review". No /approve slash command appears in the current documentation, and the approval is really the mode switch that grants write access. Mark /approve as not independently confirmed. The underlying behaviour is stable: plan mode is read-only, and execution is a distinct phase that begins only after the mode leaves plan.
Mechanism reference: What guarantees read-only behaviour
The read-only guarantee of plan mode is structural, not a matter of model compliance. The same permission engine that gates every other tool also gates writes in plan mode, so Edit, Write, NotebookEdit, and destructive Bash calls are blocked until the mode changes. A well-formed prompt cannot talk the agent past this block, and a distracted developer cannot accidentally let a planning session rewrite production. Two exceptions are worth noting: writes to protected paths are never auto-approved except in bypassPermissions and in planning sessions where bypass permissions are available; and when auto mode is available during planning, classifier-approved commands may run. For ordinary plan mode without bypass permissions, the session is effectively read-only for the purpose of the approval gate.
Mechanism reference: Example 1: mode selection at launch
The two common launch forms for this task are shown below. The first starts a session in read-only planning; the second starts an interactive session that may edit immediately but still prompts for anything not read-only.
# Start directly in plan mode for a stated complex, multi-file task.
# The agent explores, proposes an approach, and blocks all writes until you
# switch the permission mode out of plan.
claude --permission-mode plan "Restructure the billing module into a standalone service with its own API contract"
# Start in the default (Manual) mode for a well-scoped, known fix.
# Reads are free; the one-line edit prompts once and then applies.
claude --permission-mode default "Add a null check to validateUser in auth/helpers.ts line 42"The first command encodes the exam's "recognize complexity upfront" rule: because the prompt names a service extraction with an API contract, the developer chooses plan at launch rather than waiting for surprises. The second encodes the direct-execution checklist (specific change, known location, clear approach).
Mechanism reference: Example 2: the full permission mode value space
The table below is the complete value space a candidate may be tested on, with the trigger and the documented starting flag.
| Mode value | What runs without a prompt | Best for | Launch flag |
|---|---|---|---|
default (Manual) | Reads only | Reviewing every action, sensitive work | claude --permission-mode default |
acceptEdits | Reads, file edits, common filesystem commands | Iterating on code you review | claude --permission-mode acceptEdits |
plan | Reads (edits blocked until approved) | Exploring before changing | claude --permission-mode plan |
auto | Most actions, with classifier review | Long tasks, less prompt fatigue | claude --permission-mode auto |
dontAsk | Only pre-approved tools | Locked-down CI and scripts | claude --permission-mode dontAsk |
bypassPermissions | Everything (no checks) | Isolated containers or VMs only | claude --permission-mode bypassPermissions |
Note the interactive Shift+Tab cycle order: default to acceptEdits to plan and back to default, with bypassPermissions and then auto slotting in after plan when those modes are enabled. dontAsk is never in the cycle; it is flag-only. auto appears in the cycle only when the account meets the auto mode requirements.
The same value space expressed as launch flags, one per mode, makes the full set explicit:
claude --permission-mode default # Manual: reads free, everything else prompts
claude --permission-mode acceptEdits # file edits and common fs commands auto-approved
claude --permission-mode plan # read-only planning until you leave the mode
claude --permission-mode auto # classifier reviews most actions instead of you
claude --permission-mode dontAsk # only pre-approved tools run, the rest denied
claude --permission-mode bypassPermissions # no checks; isolated containers onlyEach flag sets the starting permissionMode for that session. The documented form is --permission-mode <value>; the shorthand claude --plan shown in the reference page is not independently confirmed against the current CLI reference.
Mechanism reference: Subagent definitions: tool restriction and separate context
A subagent is a specialized agent Claude delegates to. Each subagent runs in its own fresh context window with a custom system prompt, a specific tool set, and independent permissions. The fields that matter for this task are tool restriction and context separation.
A subagent is defined either as a Markdown file in .claude/agents/ (project or user scope) with YAML frontmatter, or as inline JSON passed through the --agents CLI flag or the Agent SDK agents parameter. The supported frontmatter fields include name (required), description (required), tools, model, prompt (the body), disallowedTools, permissionMode, mcpServers, hooks, maxTurns, skills, initialPrompt, memory, effort, background, and isolation. The --agents JSON form accepts description, prompt, tools, model, disallowedTools, permissionMode, mcpServers, hooks, maxTurns, skills, initialPrompt, memory, effort, background, and isolation.
The context separation rule is the key one for the Explore pattern. Each non-fork subagent starts with a fresh, isolated context. It does not inherit the parent conversation history, the skills the parent already invoked, or the files the parent already read. The only thing that reaches the subagent is the delegation message Claude composes, plus the CLAUDE.md hierarchy and a git-status snapshot (the built-in Explore and Plan agents skip CLAUDE.md and git status to keep their context small). This is why handing task-scoped findings into the spawn prompt is the developer's responsibility, not something the subagent can recover from the parent on its own.
The tool restriction rule is the key one for least privilege. The tools field limits which tools the subagent can call. A read-heavy reviewer should list only Read, Grep, and Glob, omitting Write and Edit so it physically cannot mutate the codebase it audits. Omitting Agent from its tools also prevents it from spawning further subagents. This is the deterministic counterpart to plan mode's read-only guarantee, applied at the subagent boundary instead of the session boundary.
Mechanism reference: Example 3: a read-only exploration subagent definition
The following defines a read-only exploration subagent inline for one session. It is the programmatic form of the Explore pattern: only read tools, a focused system prompt, and no write capability.
{
"codebase-explorer": {
"description": "Map dependencies and call sites for a planned refactor. Use before any edit.",
"prompt": "You are a read-only codebase explorer. List every file that imports the deprecated util, trace its call sites, and report a dependency summary with file paths. Do not propose edits.",
"tools": ["Read", "Grep", "Glob"],
"model": "inherit",
"disallowedTools": ["Write", "Edit", "Agent"]
}
}This definition is passed with claude --agents '<json>' or through the Agent SDK agents parameter. Because Write and Edit are excluded, the subagent is structurally unable to change files, and because only Read, Grep, and Glob are present, its blast radius is read-only. Its verbose file reads stay in its own context; only the summary returns to the main conversation, keeping the main context clean for the planning and execution phases.
The built-in Explore agent is the same idea shipped by default: read-only tools, Write and Edit denied, model inherited from the session (capped at Opus on the API). Claude delegates to it when it needs to search or understand a codebase without making changes. Using Explore rather than a custom subagent is the lowest-friction way to get context isolation for verbose discovery.
Mechanism reference: Permission rules that make read-only deterministic
Beyond the session mode, Claude Code supports three arrays of permission rules in settings.json (project, user, or local scope): allow, ask, and deny. These are engine-enforced, not advisory. The evaluation order is Deny, then Ask, then Allow. A deny rule blocks the call even if the user later prompts for it, and it blocks in every mode including bypassPermissions. This is the control to use when the requirement is "Claude cannot modify production config" or "Claude cannot read secrets".
Mechanism reference: Example 4: permission rules that make read-only deterministic
The settings block below enforces two hard boundaries: secret files can never be read, and destructive removals are blocked. These hold regardless of the session mode.
{
"permissions": {
"allow": [
"Read(**/*)",
"Grep(**/*)",
"Glob(**/*)"
],
"ask": [
"Write(**/*)",
"Bash(npm install *)"
],
"deny": [
"Read(.env)",
"Read(.env.*)",
"Bash(rm -rf *)",
"Bash(sudo *)"
]
}
}The deny entries take precedence over the allow entries and over permissive modes. Even in acceptEdits or bypassPermissions, Read(.env) is blocked because deny wins. Note the Read(.env.) glob is a plausible pattern but the documented examples use Read(./.env) and Read(./secrets/); treat the .env. glob form as not independently confirmed and prefer the verified Read(./.env) spelling in published material. The same precedence is what makes permissions.deny the right answer over a CLAUDE.md instruction, which is only advisory.
Mechanism reference: Advisory instruction versus enforced permission
This distinction is tested directly. A line in CLAUDE.md such as "never modify config/ or migrations/" is advisory: the model may comply or not, and a user prompt can talk it past the instruction. A permissions.deny rule, an allowedTools restriction, or a permission mode is enforced by the tool layer before the model's judgment is consulted. The documentation states plainly that putting a boundary in CLAUDE.md is advisory while putting it in permissions.deny is enforced by the tool layer itself. The exam therefore expects allowedTools or permissions.deny whenever the requirement is "cannot accidentally" rather than "prefer not to".
Mechanism reference: The hybrid plan-then-execute pattern
The reference page's central pattern is plan THEN execute, not plan OR execute. In plan mode the agent explores, maps dependencies, evaluates approaches, and designs the implementation strategy, all without modifying files. After the human approves, execution switches to a writable mode and applies the planned approach file by file. The read-only guarantee is what makes this safe: because plan mode cannot write, the approval step is a real gate, and execution is a separate phase that begins only after the mode leaves plan.
This pattern is the correct answer for library migrations, architectural extractions, and any task the reference page lists as "multiple valid approaches" or "multi-file modifications". For a trivial edit the pattern collapses to direct execution alone, because there is no planning phase worth holding.
Mechanism reference: Example 5: a plan-then-execute migration walkthrough
A logging-library migration across 30 files is the worked example in the reference page. The two phases are explicit and use different modes.
# Phase 1: plan. Start in plan mode so no file is touched while the agent
# maps every import of the old library, designs one migration pattern,
# and enumerates edge cases.
claude --permission-mode plan "Migrate every file importing old-logger to new-logger. Identify all importers, map the API differences, design one consistent migration pattern, and note edge cases."
# The agent returns a written plan and a proposed file set. You review it,
# then leave plan mode. In an interactive session press Shift+Tab until the
# mode leaves plan, or use the mode indicator. There is no /approve command
# in the current docs; the mode switch is the approval.
# Phase 2: execute. Run a normal writable session that applies the planned
# pattern file by file. Because the strategy was settled in phase 1, the
# result is consistent across all 30 files.
claude --permission-mode acceptEdits "Apply the old-logger to new-logger migration pattern to each file identified in the plan, in the planned order."The failure mode this avoids is pattern drift: if you skip plan mode and start editing directly, early files use one approach and later files use another, requiring a second reconciliation pass. Settling the pattern before the first edit makes the execution mechanical and consistent.
Mechanism reference: Adaptive decomposition inside plan mode
For entangled work, plan mode should not commit to one fixed up-front sequence. It maps the dependency graph first, then generates subtasks adaptively as each newly inspected unit reveals downstream objects it touches. This is the plan-mode counterpart of routing by task shape: the entangled minority is both planned and allowed to re-plan its subtasks as facts emerge, while the mechanical majority can go straight to direct execution. The documentation supports adaptive planning as a general agentic technique, and the lesson set frames dynamic adaptive planning as updating the plan as new information arrives rather than following a rigid sequence.
Mechanism reference: Headless runs: -p and the safety claims
The forensics file flags a contradiction about headless safety. The defensible position, confirmed by the documentation, is that claude -p runs non-interactively and prints a result, with no human in the loop, but it does not disable safety. In a -p run the built-in starting permission mode is default (Manual) on every plan, so plan mode's interactive approval gate simply does not apply; you set the mode you want with a flag. Permissions, deny rules, and the engine-enforced boundaries all still apply. Only --dangerously-skip-permissions (or bypassPermissions) skips checks, and that is reserved for isolated containers and VMs.
A second nuance: the --resume and --continue flags DO work with -p (for example claude -p "..." --continue or claude -p "..." --resume "$session_id"). What does not apply in -p is the interactive /resume slash command and the interactive plan-approval step, because those need a human. The forensics claim that "plan mode and /resume do not apply to -p" is therefore partly right (the slash command and the approval gate) and partly wrong (the --resume flag works). State it precisely: interactive plan approval and the /resume slash command are absent in -p, but the --resume/--continue flags and all permission enforcement remain.
Mechanism reference: Example: headless fan-out with pre-approved tools
For unattended work across many modules, loop claude -p once per module. Each invocation needs its tools pre-approved so it does not block on a prompt. --allowedTools grants approval for the listed tools; it does not restrict the overall surface. To restrict, scope the allowedTools entries or add permissions.deny rules.
# Fan-out: one fresh context per module, tools pre-approved, no human prompt.
for f in $(cat modules.txt); do
claude -p "Migrate $f to the new ORM signature" --allowedTools "Read,Edit,Bash(git:*)"
doneThe Bash(git:*) colon form is shown in community notes but the current documentation uses space-prefixed prefix matching such as Bash(git diff ); the space before matters. Treat Bash(git:*) as not independently confirmed and use Bash(git diff *) or Bash(git push *) in published material.
Mechanism reference: SDK subagent transport boundary
The forensics notes that an SDK agents parameter subagent runs in a fresh isolated context, and pairs this with MCP server connection. The transport boundary is real: a remote, HTTP-exposed MCP server can be wired through the Messages API mcp_servers, while a local stdio server needs the SDK's mcpServers option because the application must spawn and manage it. Including Agent in allowedTools auto-approves delegation in unattended runs. This is the programmatic expression of the Explore/reviewer pattern and the least-privilege rule.
Mechanism reference: Tool-pattern grammar in full
The permission rule syntax is the same whether written in settings.json or passed to --allowedTools. The grammar has three shapes:
- Built-in tool with a parenthesized pattern:
Tool(pattern). ForRead,Grep,Glob,Edit, andWrite, the pattern is a glob over paths, for exampleRead(/),Edit(.claude/),Write(payments/). ForBash, the pattern matches a command; prefix matching uses a space before the soBash(git diff )allows any command starting withgit diff, whileBash(git diff)(no space) would also matchgit diff-index, which is usually not intended. Documented examples includeBash(npm test),Bash(git push *), andBash(git commit *). - MCP tool with a
mcp__server__toolname: the wildcard tool name is only valid after the literalmcp__<server>__prefix. A bare wildcard tool name does not match MCP tools; to allow all tools of a server you writemcp__server__*. - Skill rules:
Skill(name)for an exact match andSkill(name *)for a prefix match with any arguments. These govern which skills Claude may invoke, not file or command access.
The ask array is the middle of the precedence. A rule in ask prompts the user instead of auto-allowing or auto-denying; an ask rule that matches on a command's content, such as Bash(git push *), falls back to a permission prompt even when an allow rule would otherwise match. The deny array is the top of the precedence and blocks in every mode, including bypassPermissions, except for the narrow actions no mode auto-approves (such as rm/rmdir on critical paths, which deny rules also block). This is why a "cannot accidentally delete production config" requirement is met by a permissions.deny entry, not by an allow entry or a CLAUDE.md line.
The colon form Bash(git:*) that appears in some community notes is not the documented prefix-matching form; the documentation uses the space-prefixed Bash(git diff ) style. Treat Bash(git:) as not independently confirmed. Likewise Read(.env.) is a plausible glob but the verified examples use Read(./.env) and Read(./secrets/); prefer those spellings in published material and mark the .env. glob not independently confirmed.
Mechanism reference: The /permissions command and IDE controls
Beyond the Shift+Tab cycle, an interactive session can switch modes with the /permissions command, and the IDE integrations expose a mode indicator (VS Code) or mode selector (Desktop) that performs the same switch. In VS Code the indicator labels are Manual, Edit automatically, Plan, Auto, and Bypass permissions; picking Plan or Bypass permissions applies to that conversation only, while Manual, Edit automatically, or Auto can persist as the last-picked mode. The Desktop selector shows Auto and Bypass permissions only when the account meets the respective requirements. Asking Claude in chat to change the mode does not work in any interface; you must use the control.
This matters for the exam because the "leave plan mode" step can be described several ways: press Shift+Tab until the mode leaves plan, click the mode indicator out of Plan, run /permissions and choose a writable mode, or start a separate writable session. All are the same approval event: the session obtains write access only after the mode is no longer plan. The undocumented /approve command should not be taught as the mechanism.
Mechanism reference: Custom commands and the plan_mode frontmatter
A custom command (a Markdown file with YAML frontmatter, now merged with skills) can set plan_mode: true to force plan mode for that command. If the frontmatter omits plan_mode, the command follows whatever mode the session is already in. So a command run inside a session started with --permission-mode plan will propose a plan first, while the same command in a normal session runs directly. This is the documented basis for the reference page's claim that a command without plan_mode inherits the session mode. A plan_mode: true flag overrides the inheritance and forces plan mode regardless of the session.
The exam may ask what happens when such a command runs in a direct-execution session: the answer is that without plan_mode it inherits direct execution, and with plan_mode: true it switches to plan mode for that command. Neither case involves the agent inferring the mode; the mode is determined by the developer's session choice or the explicit frontmatter flag.
Ownership map
This section states which layer owns each guarantee, because the exam sometimes asks where a behaviour actually comes from.
- Model layer: chooses whether to plan or execute in its reasoning, but does NOT own mode selection. The mode is the developer's explicit choice; the model does not infer complexity and switch modes on its own. Any "automatic mode selection" is wrong.
- CLI layer: owns the
permissionModesetting, theShift+Tabcycle, the--permission-modeflag, the--allowedToolsflag, and the-pnon-interactive entry point. The CLI passes the mode into the engine. - Permission engine layer: owns the deterministic gate. It enforces plan mode's write block, the
allow/ask/denyevaluation order, and the protected-path and critical-path checks. This is the layer that makes read-only behaviour structural rather than advisory. - Configuration layer (settings.json, CLAUDE.md):
settings.jsonpermission rules are engine-enforced;CLAUDE.mdinstructions are advisory and owned by the model's compliance, not the engine. This split is the answer to advisory-versus-enforced questions. - SDK or application code layer: owns subagent definitions when using the
--agentsflag or the Agent SDKagentsparameter, including tool restriction, isolated context, and MCP transport wiring. - Infrastructure layer: owns isolation for
bypassPermissions(containers, VMs, sandbox runtimes). The documentation restricts that mode to isolated environments.
Version and terminology currency
The terminology has shifted across releases, and a candidate may meet older names in the exam guide than the current tool uses.
- The mode that reviews every action is now labelled Manual in the CLI, VS Code, JetBrains, and Desktop, with the config value
default. Themanualalias fordefaultrequires Claude Code v2.1.200 or later. Older material may call it "default mode" without the Manual label; both refer to the samedefaultvalue. automode is a recent addition. It can only be set in user-level settings (not project or local) as of v2.1.142, and the built-in starting default isautoon Pro, Max, and Team plans from v2.1.228 (terminal/macOS/Linux/WSL) and v2.1.233 (native Windows). Before those versions the built-in default is Manual. A candidate should not assumeautois the default everywhere.dontAskis the current name for the locked-down CI mode; it never appears in the interactive cycle and is flag-only. The reference page's framing of CI safety maps todontAskpluspermissions.deny, not to plan mode.acceptEditsauto-approves file edits and common filesystem commands. Earlier guidance sometimes described a narrower "edit approval" behaviour; the current rule includesmkdir,touch,mv,cpand similar.bypassPermissionsis the current name for the skip-all-checks mode;--dangerously-skip-permissionsis equivalent. It can be blocked org-wide viapermissions.disableBypassPermissionsMode.- The
planvalue itself is stable across these releases; the entry and exit mechanics (flag,Shift+Tabcycle, mode switch) are consistent. The unstable parts are the shorthandclaude --planand the/planand/approvecommand spellings, which arenot independently confirmedagainst current docs.
Official versus community divergence
Where community material contradicts the documentation, the documentation wins. The documented positions below are what a candidate should answer with.
- Community claim:
claude -pdisables safety checks. Documentation position:-pis non-interactive and skips the interactive approval gate and the/resumeslash command, but permissions, deny rules, and engine-enforced boundaries still apply. Only--dangerously-skip-permissionsskips checks, and that is container-only. Answer with the documentation position. - Community claim: plan mode is the general CI safety mechanism. Documentation position: plan mode is a design gate chosen for ambiguity, not an environment control. CI safety comes from
--allowedToolspre-approval andpermissions.deny, often withdontAskas the baseline. Answer with the documentation position. - Community claim: there is an
/approvecommand that executes the plan in place. Documentation position: no/approvecommand is documented; approval is the permission-mode switch that grants write access, and execution is a separate phase. Mark/approveasnot independently confirmedand describe the mode switch. - Community claim: the agent auto-selects plan or direct execution from task complexity. Documentation position: mode selection is the developer's explicit decision; the agent does not infer complexity. Answer with the documentation position; any "automatic inference" option is wrong.
- Community claim: a
CLAUDE.mdrule such as "never run rm -rf" is enough to block destructive commands. Documentation position: that is advisory and can be overridden;permissions.denyis the enforced control. Answer with the documentation position.
Beyond the task statement
The reference page covers plan mode and direct execution, but our lesson set covers adjacent material the reference page omits. A strong candidate knows these, because the exam asks scenario questions that draw on them.
core-capabilities(claude-code domain). This is the foundational lesson for this task. It documents the full permission mode system (default,acceptEdits,plan,auto,dontAsk,bypassPermissions), theallow/ask/denyrule arrays, the Deny to Ask to Allow precedence, protected paths, and the agent loop. It is the primary citation for the read-only guarantee and the advisory-versus-enforced distinction.
workflow-patterns(claude-code domain). Covers the CI/CD flags that interact with mode choice:-pfor non-interactive runs,--allowedToolsfor pre-approval,--permission-modeas a baseline,--output-format jsonfor structured consumption, and session isolation between generation and review. It also documents the five workflow patterns (prompt chaining, routing, parallelization, orchestrator-subagents, evaluator-optimizer) and the anti-pattern of self-review in the same session. This matters because headless fan-out is the real-world home ofdontAskpluspermissions.deny.
claude-code-fork-session(claude-code domain). Documents/forkas a subagent that inherits the full parent conversation, unlike a regular subagent's fresh context. Forking is the wrong tool for a simple fix but the right tool for comparing divergent approaches from a shared baseline. This is the conceptual neighbour of the Explore pattern: both isolate work, but a fork shares history while a subagent starts fresh. The lesson also covers--resume/--continue, checkpoints, and worktree isolation.
planning-reasoning(agentic-architecture domain). The conceptual basis for the hybrid pattern. It documents Plan-Execute (plan separated from execution for inspectable, human-reviewable strategy), dynamic adaptive planning (update the plan as new information arrives), and the anti-pattern of Plan-Execute without replanning hooks. The exam's "plan then execute" maps directly onto the Plan-Execute pattern, and "adaptive decomposition" maps onto dynamic adaptive planning.
claude-code-slash-commands(claude-code domain, referenced as skills). Documents that custom commands are Markdown files with YAML frontmatter, and that aplan_mode: truefrontmatter flag forces plan mode for that command while its absence inherits the session mode. This is the documented basis for the reference page's claim that a custom command withoutplan_modefollows the session mode. The lesson also coversallowed-toolsgrants in frontmatter, which are engine-enforced during the invoking turn.
configuration,best-practices,mcp-integration,claude-code-print-pref, andclaude-code-mdc-config(claude-code domain). Adjacent lessons that round out the permission and execution story:configurationcoverssettings.jsonscope and precedence;best-practicescovers review discipline;mcp-integrationcovers how MCP tools enter the permission system asmcp__server__tool;claude-code-print-prefcovers the-p/--printnon-interactive form;claude-code-mdc-configcovers Memory-driven configuration that loads alongside permissions.
- The agents-sdk domain (
agent-class-lifecycle,tool-binding,rpc-methods) is the programmatic home of subagent definitions via the SDKagentsparameter, theAgenttool, and MCP transport wiring. A candidate who works at the SDK level should know that subagents there run in fresh isolated contexts and thatAgentmust be inallowedToolsfor unattended delegation.
Worked production examples: Worked example A: monolith extraction with upfront complexity recognition
Scenario: the requirements state "extract the payments service from the monolith into its own module with a defined API contract". This is stated architectural, multi-file work, so the developer selects plan mode at launch.
claude --permission-mode plan "Extract the payments service from the monolith. Map every call site, design the new API contract, identify shared data handling and transaction boundaries, and propose a migration sequence."
During planning the agent reads widely, traces dependencies, and proposes an approach. Crucially it discovers a custom transaction wrapper the legacy service uses, which a direct-execution start would have missed until file 9. Because planning happened first, the wrapper is part of the design, not a mid-implementation surprise. The developer reviews the proposed file set and strategy, then leaves plan mode (the mode switch is the approval) and executes the planned sequence in a writable session. The observable outcome is a consistent extraction with no half-migrated inconsistency.
Worked production examples: Worked example B: direct execution for a single well-scoped fix
Scenario: a stack trace names validateUser at auth/helpers.ts line 42, and the failure is a missing null check. The change is specific, the location is known, and the approach is clear, so the checklist says direct execution.
claude --permission-mode default "Add a null check to validateUser in auth/helpers.ts line 42 to guard against an undefined user object."
The agent reads the function, applies the one-line edit, and the permission prompt appears once for the edit. Total time from prompt to fix is short. Using plan mode here would add a round-trip with no design value, which is the overhead trap the reference page warns against. The observable outcome is a single edited file and a fast fix.
Worked production examples: Worked example C: a read-only security reviewer in CI fan-out
Scenario: a pipeline must review 1800 modules for injection risk without modifying anything. A read-only reviewer subagent is defined with tools: ["Read", "Grep", "Glob"] and disallowedTools: ["Write", "Edit", "Agent"], then invoked per module in a -p loop with --allowedTools "Agent" so delegation is pre-approved.
claude -p "Review the given module for injection risks and return a concise report" \
--agents '{"security_reviewer":{"tools":["Read","Grep","Glob"],"prompt":"Review for injection risks. Return a concise report. Do not modify files."}}' \
--allowedTools "Agent"Each module gets a fresh context, the reviewer cannot write, and only the report returns. This combines the least-privilege rule (read-only tools) with the context-hygiene rule (verbose reads stay in the subagent). The observable outcome is 1800 isolated reviews with no codebase mutation and a clean main context.
Worked production examples: Protected paths and critical paths: the read-only guarantee in detail
Plan mode blocks ordinary writes, but two special path classes shape what even a writable mode can do, and they matter for the "safe review" story. Protected paths are a small set of directories and files whose writes are never auto-approved except in bypassPermissions mode and in planning sessions where bypass permissions are available. The protected directories include .git, .vscode, .claude (except .claude/worktrees), and several tool-config directories; the protected files include shell rc files, .npmrc, .mcp.json, and similar. In plan mode a protected-path write is routed to the classifier when auto mode is available during planning, and prompted otherwise; in dontAsk it is denied; in bypassPermissions it is allowed.
Critical paths guard against destructive removal. rm or rmdir targeting the filesystem root, a top-level directory, the home directory, the working directory or its parents, or a glob under a shell variable, is never approved by an allow rule or a PreToolUse hook, even in modes that skip other prompts. This circuit breaker protects against model error. In plan mode such a removal prompts (or goes to the classifier when auto is available); in dontAsk it is denied. Hiding the removal inside command substitution or process substitution does not skip the check.
The practical reading for this task: plan mode's read-only guarantee is the baseline that makes human review possible, but the truly dangerous operations (protected config writes, critical-path removals) are additionally guarded in every mode. A candidate who says "plan mode prevents all dangerous writes" is overstating it; plan mode prevents ordinary edits, while the deny rules and critical-path logic provide the deeper guard rails. The correct layered mental model is: plan mode blocks edits until approval, permissions.deny blocks specific operations in every mode, and critical-path logic blocks catastrophic removals regardless of allow rules.
Worked production examples: The classifier and auto mode interaction with plan
When auto mode is available during planning, the plan row of the mode table shows "Reads, plus classifier-approved commands". This means that in a plan-mode session where bypass permissions are not available but auto mode is, some commands may still run if the classifier approves them, even though file edits remain blocked. The classifier reviews actions using a fixed decision order: allow/ask/deny rules resolve first, read-only actions and working-directory edits are auto-approved (except protected paths), and everything else goes to the classifier. Writes to protected paths and critical-path removals still route to the classifier or prompt rather than being silently approved.
This nuance answers a subtle exam question: is plan mode purely read-only? The precise answer is that plan mode blocks edits and writes as the design gate, but when auto mode is available it may let classifier-approved commands run. The safe, exam-stable statement is that plan mode blocks file edits and writes until you review and approve a plan; the classifier detail is the exception a candidate should mention only when the question is about auto mode during planning.
Worked production examples: Fork versus subagent: context inheritance matters
The read-only exploration pattern is one use of subagents, but a fork is a different tool and the exam may contrast them. A fork is a subagent that inherits the entire parent conversation: the same system prompt, tools, model, and message history. A regular (non-fork) subagent starts fresh from its definition and the delegation prompt. The fork's own tool calls stay out of the main conversation and only its result returns, so the main context stays clean, but because it shares history it is more expensive in tokens unless that history is needed. A fork cannot spawn further forks. Forking is the right tool for comparing divergent approaches from a shared baseline, not for a simple fix and not for discovery isolation; for discovery isolation the fresh-context subagent (or the built-in Explore) is the better fit.
The exam contrast is: use a fresh subagent (or Explore) when the side task would flood the main context with reads it will not reference again; use a fork when the side task needs the parent's existing context to be useful, such as comparing two implementations that both depend on decisions already made in the conversation. Both keep the main context clean, but only the fork avoids re-explaining the situation. This is the same "separate context" rule from the subagent section, viewed from the inheritance side.
Decision framework and boundary conditions
The reference page reduces mode selection to ambiguity versus difficulty. This section makes that operational with the full boundary logic the exam tests.
Decision framework and boundary conditions: The ambiguity checklist
The direct-execution checklist has three questions: is the change specific, is the location known, is the approach clear. If all three are yes, direct execution is correct. Any no pushes toward plan mode. This is the same test the documentation implies when it separates "Exploring a codebase before changing it" (plan) from the baseline where reads are free and edits prompt (direct). The checklist is the developer's tool, not the model's; the model does not run it automatically.
Decision framework and boundary conditions: File count is a heuristic, not the criterion
A ten-file, well-scoped, clear-approach change can be direct execution, while a two-file change with a genuine fork can need plan mode. File count correlates with the real triggers (exploration need, approach diversity, architectural consequence) but is not equal to them. A broad mechanical sweep with no design fork is safe to execute directly even if large; a tiny change that forces a structural choice is not. The exam rejects "plan for multiple files, direct for single" as the criterion.
Decision framework and boundary conditions: Sensitivity, urgency, and environment do not force plan mode
A fully specified, urgent, single-line security fix is direct execution despite the security domain. A well-scoped fix inside a CI review is direct execution despite the automated environment. The right place for sensitivity controls is the permission system (allowedTools scoping or permissions.deny), never the planning mode. Using plan mode as a blanket CI safety mechanism misreads its purpose; CI safety is achieved through pre-approved --allowedTools and permissions config, often with dontAsk as the baseline.
Decision framework and boundary conditions: Recognize complexity upfront
When the requirements already name architectural decisions and many files, plan mode should be chosen immediately. Waiting until complexity emerges during direct execution is the wrong move because the complexity was never speculative; it was in the requirements from the start. If a task looked simple but a genuine fork appeared mid-work, switching to plan mode then is appropriate and is not the trap; the trap is only when the complexity was foreseeable. This is the "complexity is known, not speculative" rule.
Decision framework and boundary conditions: Cross-rule interactions
Several rules combine, and when they do one dominates. The ambiguity test dominates the difficulty test: a hard but unambiguous fix is direct execution, while an easy but forked feature is plan mode. The read-only guarantee dominates the hybrid handoff: no matter how well a plan is designed, execution must leave plan mode because plan mode cannot write. Context hygiene dominates speed: an Explore or review subagent is chosen to protect the main window, not to save wall-clock time, so framing the subagent as a speed tool is wrong. Permission controls dominate mode choice for safety: sensitivity is handled by allowedTools scoping or permissions.deny, never by selecting plan mode. Routing by task shape dominates one-mode-for-all: within a single migration the mechanical majority goes direct while the entangled minority plans, and the entangled minority uses adaptive decomposition rather than a fixed sequence. When a custom slash command omits plan_mode, the session mode dominates, because the human's explicit choice is inherited, not inferred.
Decision framework and boundary conditions: Plan mode previews a proposed change as a diff
A core use of plan mode is letting the human review the intended approach on a large or hard-to-reverse change before execution. The agent proposes which files, what changes, and in what order, and the human can adjust before any edit lands. This is framed in the documentation as one of the two appropriate uses of plan mode: "reviewing a proposed multi-file refactor before edits are applied" and "letting Claude outline a migration strategy you can approve or adjust". The read-only guarantee makes the preview honest, because the proposal cannot silently edit. The wrong associations are "bypassing permission prompts" and "permanently disabling Bash"; plan mode is read-only design, not prompt skipping.
Decision framework and boundary conditions: Few-shot examples for strict formatting transformations
Adjacent to plan mode is a prompt-engineering rule that the lesson set covers: for strict formatting, translation, or data transformation tasks (for example INI to YAML), concrete input/output examples anchor the output format far more reliably than long prose instructions. The exam may pair this with plan mode when the planned transformation rule must be communicated precisely. The point is that a transformation's correctness lives in exact syntax, which prose describes poorly but examples show exactly. This is not a plan-mode mechanism itself, but it is how a planned transformation rule is often communicated, and it belongs in the candidate's toolkit for content-update migrations.
Decision framework and boundary conditions: Scenario inventory and the correct mode
The exam recurs across a small set of story settings, each probing a specific distinction. The table below is the decision map a candidate should internalize. Each row pairs a concrete story with the single signal that decides the mode, so the candidate can filter out the noise (how algorithmically hard the fix is, how many files the migration touches, how sensitive the domain sounds) and act on the one axis that matters: ambiguity. Memorizing the rows is less important than memorizing the axis, because the same distinction reappears under different story dressing in every sitting.
| Scenario | Signal | Mode | Why |
|---|---|---|---|
| Monolith to microservice split | Architectural, many files, competing approaches | plan | Structural choices have cross-file consequences |
| Library or framework migration across many files | Uniform conversion pattern needed | plan then execute | Settling one pattern prevents drift |
| Single-function bug with clear stack trace | Precise location, known cause | direct | No exploration or design needed |
| Fully specified security fix | Known, isolated, single line | direct | Urgency and domain do not force plan |
| Documentation or terminology refresh across services | Uniform voice and terms | plan | A glossary and template prevent drift |
| CI or headless automated review | No human in loop | direct with pre-approved tools | Mode choice is explicit; safety via permissions |
| Verbose cross-package discovery | Unknown structure, noisy output | plan with Explore subagent | Isolation keeps main context clean |
| Strict config transformation | Exact syntax required | direct with few-shot examples | Format anchored by examples, not prose |
The through-line is the ambiguity axis. Every correct answer reduces to whether the task has an open decision, an unknown location, or an unexplored dependency. Every distractor adds a false axis: difficulty, file count, domain sensitivity, or environment.
Decision framework and boundary conditions: Common distractor families
The wrong answers in the item bank follow a small number of families. Knowing them helps a candidate eliminate options quickly.
- The automatic-inference distractor: claims the agent picks the mode from complexity. Wrong; the developer chooses.
- The domain-forces-plan distractor: claims security, database, or CI work must be planned because the area is sensitive. Wrong; only ambiguity forces planning.
- The difficulty-forces-plan distractor: claims hard tasks need planning regardless of ambiguity. Wrong; a hard but unambiguous fix is still direct.
- The plan-auto-executes distractor: claims approval runs the plan in place. Wrong; plan mode is read-only and a separate phase applies it.
- The plan-for-the-whole-duration distractor: claims plan mode should cover the edits too. Wrong; plan mode cannot write, so execution must leave it.
- The wait-for-surprises distractor: start direct, switch to plan only when complexity appears, for stated-complexity tasks. Wrong; plan up front.
- The parallel-sessions distractor: fan out one session per file in direct execution. Wrong; parallel contexts cannot share a strategy, so consistency fails.
- The CLAUDE.md-is-deterministic distractor: a config instruction blocks an action. Wrong; only
allowedToolsorpermissions.denyare engine-enforced. - The subagent-for-speed distractor: use the Explore subagent to go faster. Wrong; the reason is context hygiene, not speed.
- The fork-for-simple-fix distractor: fork to keep context clean on a one-line edit. Wrong; forking compares divergent approaches, not obvious fixes.
Decision framework and boundary conditions: A note on mode as the developer's explicit decision
The documentation is explicit that plan mode versus direct execution is chosen by the developer. The agent does not analyze task complexity and select a mode automatically, and there is no "complexity threshold configured in CLAUDE.md for automatic mode selection". The exam may offer these as plausible-sounding single-select answers; they are wrong. The developer selects the mode through Shift+Tab, /permissions, the mode indicator, the --permission-mode flag, or a custom command's plan_mode frontmatter. This is why the reference page's practice scenario answer is Option A: the developer decides plan for the monolith split and the migration, and direct for the single-file fix.
Decision framework and boundary conditions: Summary of the three guarantees
This task rests on three guarantees that a candidate should be able to state precisely, because the exam probes each one from a different angle.
The first guarantee is read-only planning. In plan mode the permission engine blocks Edit, Write, NotebookEdit, and destructive Bash calls until the mode changes. Reads, searches, and reasoning run normally, so the agent can explore, analyze dependencies, and propose an approach without touching the filesystem. This is structural, not a polite request, so a well-formed prompt cannot bypass it and a distracted developer cannot accidentally let a planning session rewrite production. The one documented exception is when auto mode is available during planning and the classifier approves a command; file edits remain blocked regardless.
The second guarantee is the approval gate. Because plan mode cannot write, the human can review the full proposal, the intended file set, and the ordered changes before granting any write access. This review gate is what makes plan mode a governance tool rather than merely a thinking aid. The gate is crossed by switching the permission mode out of plan (through Shift+Tab, the mode indicator, /permissions, or a separate writable session); there is no in-place execution. The reference page's /approve wording is a paraphrase of this switch and is marked not independently confirmed as a command name.
The third guarantee is deterministic permission scoping. Whether through a session mode, an allowedTools restriction, or a permissions.deny rule, the boundary is enforced by the tool layer before the model's judgment is consulted. This is what makes "Claude cannot modify production config" a real guarantee rather than a hope. The contrast with CLAUDE.md instructions is the cleanest test: an instruction in CLAUDE.md is advisory and can be overridden by the model or by a user prompt, while a permissions.deny rule is engine-enforced and wins over allow rules and over permissive modes. The exam expects the enforced control whenever the requirement is "cannot accidentally".
Together these three guarantees explain why the hybrid pattern is plan THEN execute. Planning is safe because of guarantee one and two; execution is consistent because the strategy was settled before the first edit; and both phases are bounded by guarantee three when the task touches sensitive areas. None of the guarantees depends on the model's good behaviour, which is exactly why they are reliable under exam scrutiny.
Decision framework and boundary conditions: A compact recap of the decision rule
When a candidate faces a scenario, the shortest correct reasoning chain is: ask whether the task has an open decision, an unknown location, or an unexplored dependency. If yes, plan mode (and often the Explore subagent for verbose discovery). If no, on the three-question direct-execution checklist, direct execution. Difficulty, file count, domain sensitivity, and environment are not the deciding axes. When the task is large and entangled, plan the strategy then execute it file by file, routing the mechanical majority to direct execution and the entangled minority to adaptive planning. The mode is always the developer's explicit choice, never the agent's inference. Holding this chain resolves every scenario in the inventory above.
Build exercise material
These exercises are verifiable: each step has an observable outcome that proves the step worked. They map to the reference page's build exercises and to the mechanism sections above.
Build exercise material: Exercise 1: plan mode for a complex multi-file task
Steps:
- Pick a real refactoring in a repository, for example moving a module into a new package.
- Launch
claude --permission-mode plan "<refactor description>". - Ask the agent to identify dependencies, list approaches with tradeoffs, and recommend a strategy.
- Inspect the working tree with
git statusandgit diff --stat.
Observable outcome: the agent produces a written plan with a recommended approach and a proposed file set, and git status shows no modified files. The read-only guarantee held. If any file changed, the mode was not actually plan (verify with the mode indicator).
Build exercise material: Exercise 2: direct execution for a single-file fix
Steps:
- Reproduce a clear, localized bug with a known stack trace pointing at one function.
- Launch
claude --permission-mode default "<one-line fix description>". - Time the prompt-to-fix interval.
Observable outcome: the agent makes the fix immediately, the change is confined to one file or function, and the interval is noticeably shorter than the plan-mode task. Plan mode would have added overhead with no design value.
Build exercise material: Exercise 3: the hybrid plan-then-execute migration
Steps:
- Choose a library migration touching many files, for example a logging or HTTP client swap.
- In plan mode, have the agent list every importing file, map API differences, and design one migration pattern.
- Leave plan mode via the
Shift+Tabcycle or mode indicator. - In a writable session, apply the pattern file by file in the planned order.
- Run
git diff --statand a build or test command.
Observable outcome: the diff is consistent across all files (one pattern), the build passes, and no second reconciliation pass was needed. Pattern drift is absent because the strategy was settled before the first edit.
Build exercise material: Exercise 4: Explore subagent context isolation
Steps:
- Pick a verbose discovery task, for example tracing a deprecated util across packages.
- Delegate it to the built-in
Exploreagent or to a custom read-only subagent (see Example 3). - In the main conversation, confirm the verbose file listings and excerpts are not present; only the summary arrived.
Observable outcome: the main context stays focused, later responses in the main conversation remain high quality, and the subagent's tool calls stayed in its own context. This proves the context-hygiene rule, not a speed gain.
Build exercise material: Exercise 5: a written decision framework
Steps:
- Write down the ambiguity checklist: is the change specific, is the location known, is the approach clear.
- For each of four plan-mode triggers (large-scale change, multiple valid approaches, architectural decision, multi-file modification) and three direct-execution triggers (well-scoped fix, known location, known approach), write a concrete example.
- State explicitly that the axis is ambiguity, not difficulty.
Observable outcome: a framework with at least four plan-mode criteria and three direct-execution criteria, each with a concrete example, and an explicit statement that difficulty does not determine the mode. A difficult but unambiguous fix is filed under direct execution; an easy but forked feature is filed under plan mode.
Build exercise material: Exercise 6: enforced deny versus advisory instruction
Steps:
- Add
Read(.env)andBash(rm -rf *)topermissions.denyin.claude/settings.json. - In the same session, add a
CLAUDE.mdline "never read .env". - Attempt to read
.envand attemptrm -rfon a non-critical path.
Observable outcome: both attempts are blocked by the engine regardless of any prompt, because deny wins. The CLAUDE.md line is advisory and does not add a hard block; only the deny rule does. This proves the advisory-versus-enforced distinction.
Mechanism and API surface
- /plan read only exploration
- Invoked via /plan, reads and analyses dependencies and tradeoffs, produces a structured plan with dependencies, approaches, and recommendation, does not modify files, enforces behavior constraint on the same runtime.
- Explore subagent isolation
- Bundled subagent invoked via Task or Agent tool, owns its own context window, returns summary to main session, primary benefit is context isolation keeping verbose listings out of the implementation window.
- Plan then execute hybrid
- Plan phase designs migration pattern and edge handling across many files, execute phase applies pattern file by file consistently. Canonical for 30 file library migrations where single pass would diverge per file.
- --permission-mode plan for headless
- In -p mode restricts session to read only for plans or risk reports without touching the repo, useful for CI planning steps that should not edit, not a replacement for iterative interactive plan mode.
- Pre ship trio /diff /code-review /security-review
- /diff shows working directory changes, /code-review bundled skill reviews diff with --fix and ultra cloud multi agent option, /security-review deeper pass, together they form see then review then fix before shipping.
- Parallel and background companions
- /agents manager, /tasks background list, /background detach, /batch worktree decomposition, /btw aside without bloating history, all compose with plan mode for multi phase orchestration.
The decision rules in play
Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.
Plan mode is a read-only, file-modification-blocked state by construction
When the agent is in plan mode, the local CLI engine refuses every tool call that would mutate the filesystem or external state. Reads, searches, and reasoning run normally, but Edit, Write, NotebookEdit, and destructive Bash calls are blocked until the session leaves the mode. The agent explores the codebase, analyses dependencies, and produces a written proposal, and only after the human approves does a separate phase obtain write access. The block is enforced by the same permission engine that gates every other tool, so it is deterministic rather than a matter of model compliance.
# Three equivalent ways to enter the read-only planning state
Shift+Tab Shift+Tab # press twice inside an interactive session
/plan # slash command form
claude --permission-mode plan "refactor the auth module to JWT" # CLI form
claude --plan "refactor the auth module to JWT" # shorthand formThe entire value proposition of plan mode is "design before implementation". If the agent could quietly edit files while the human believes it is merely planning, the review boundary would be meaningless. The read-only guarantee is what makes the approval step a real gate. Because the restriction is structural, a well-formed prompt cannot talk the agent past it, and a distracted developer cannot accidentally let a plan-mode session rewrite production.
Boundary. The boundary is the write operation itself. Reading fifty files, tracing request flows, and even typing out a full migration script in a plan document are all permitted. The moment a file would actually change on disk, the mode blocks it. The nearby opposite case is direct execution, where the same analysis is followed immediately by edits. The flip is governed by whether the human has approved a plan yet, not by what the agent has learned.
Recurring specifics. Activation is shown three ways in the tested material, and all three land on the same read-only state. The interactive key chord is Shift+Tab pressed twice to cycle the session's permission mode into plan. The slash form is /plan. The CLI form is claude --permission-mode plan or the shorthand claude --plan. the tested material also shows /approve as the action that exits the mode, and /plan (while already in plan mode) as the command that surfaces the proposed diff-like changes for review. Exact key chords and flag spellings should be verified against the current CLI reference, because the tested material-pack wording is paraphrased rather than copied from shipped docs.
Proposal. plan mode blocks all reads.
Why it attracts. it sounds like a strict sandbox.
Why it fails. exploration is the whole point of plan mode, so reads must run.
When it would be right. never, in any shipping build.
- One mutation adds "the agent keeps reading files indefinitely" as a distractor; the fix is that plan-mode exploration is goal-directed, not open-ended. Another mutation inverts the order, claiming plan mode executes on approval; the fix is that execution requires an explicit switch or an
/approvethat transitions to a normal session.
Plan mode never auto-executes; approval hands off to a separate execution phase
After the agent presents a plan, nothing on disk changes unless the human approves and the session moves into a writable phase. the tested material frames this as a two-phase handoff: plan mode designs, then direct execution implements. The approval action (commonly shown as /approve) transitions the agent out of plan mode so it can apply the described edits and tool calls. The phases are distinct on purpose; collapsing them removes the review gate that makes plan mode useful.
# After reviewing the proposal inside plan mode:
/approve # exits plan mode, the agent begins applying the described editsThe benefit of plan mode is the chance to course-correct before any commitment. If approval silently triggered the same agent to start editing, a developer who approved "in principle" could watch an unexamined detail run amok. Keeping design and implementation as separate phases preserves the moment of human agency. the tested material repeatedly contrasts "plan mode automatically executes the approved plan" against "the developer switches to direct execution to implement", and consistently marks the switch as correct.
Boundary. The boundary is the approval-plus-mode-exit event. Before it, every write is blocked. After it, the planned approach is applied with normal tool access. The nearby opposite case is a single-session hybrid where the same context that planned now edits; the tested material treats that as acceptable only after the explicit transition, not as plan mode editing in place.
Recurring specifics. The transition is /approve , which "moves out of plan mode and starts executing the planned changes". The CLI flag claude --plan followed by a prompt that needs no changes yields a plan stating "no changes needed" and waits for approval rather than looping or refusing. The --permission-mode plan flag sets the starting mode; leaving it requires the approve action or re-entering a normal session. The exact spelling of the approve command should be confirmed against current docs, since the tested material uses /approve informally.
Proposal. plan mode auto-executes on approval.
Why it attracts. saves a step.
Why it fails. the tested material is explicit that execution is a distinct phase triggered by a switch or approve.
When it would be right. only in a customised pipeline, not the default tested behaviour.
- The mutation that swaps "switch to direct execution" for "restart plan mode to re-verify" tests whether the learner understands that re-exploration after a plan is approved is wasted work.
Mode selection is keyed to ambiguity, not raw difficulty
The decision between plan mode and direct execution turns on whether the task has open questions: unknown scope, several valid approaches, architectural choices, or a codebase that must be understood before acting. Difficulty alone does not push a task into plan mode. A brutally hard but fully specified fix stays direct execution; a trivial-sounding feature with three reasonable implementations stays plan mode.
Direct execution checklist:
(1) change is specific ............ yes / no
(2) location is known ............. yes / no
(3) approach is clear ............. yes / no
All yes -> direct execution
Any no -> consider plan modePlan mode exists to spend a round-trip resolving ambiguity before commitment. If the ambiguity is absent, the round-trip buys nothing regardless of how hard the work is. Conversely, a task that looks easy but hides a design fork will burn far more time if the fork is discovered mid-edit than if it were settled up front. the tested material states this as a headline principle: "the decision is not about difficulty but about ambiguity."
Boundary. The boundary is the presence of a genuine unresolved decision. A precise stack trace naming a function with a known failure type is unambiguous even if the algorithm is subtle. The nearby opposite case is a one-paragraph feature request that could be built three ways and touches several modules; that is ambiguous even though each individual edit is easy.
Recurring specifics. the tested material pairs a "complex bug with a clear stack trace" against a "simple feature with multiple approaches" and marks the bug as direct execution and the feature as plan mode. The phrasing "a very difficult but well-defined bug fix is still direct execution" recurs. The decision checklist reduces to three questions: is the change specific, is the location known, is the approach clear. Any "no" leans toward plan mode.
Proposal. any production change carries architectural risk, so plan everything.
Why it attracts. safety-first framing.
Why it fails. a one-line null check in a known helper has no open decision.
When it would be right. only if a hidden dependency later surfaces, which the tested material treats as a separate discovery, not a reason to pre-plan every edit.
- Mutations swap the domain label to make a well-scoped change look architectural: a single pagination function with an off-by-one is offered as plan mode "because pagination is architectural". The fix is that the specific bug is bounded, so the general domain complexity is irrelevant. The inverse mutation offers a "simple feature" that is actually a fork in the road; the fix is to plan despite its apparent ease.
Multiple valid approaches is a decisive plan-mode trigger
When a task can be solved in several defensible ways with different tradeoffs, the agent should explore and compare them before committing. Plan mode is the structured home for that comparison: the agent enumerates the approaches, weighs their consequences, and presents one for approval. the tested material treats "there are two viable integration approaches with different infrastructure tradeoffs" as a textbook plan-mode signal even when the file count is modest.
Plan-mode approach comparison (illustrative structure of the proposal):
Approach A: middleware-based integration
- infra footprint: shared gateway, no per-handler boilerplate
- tradeoff: harder to opt a single route out of the chain
Approach B: decorator-based integration
- infra footprint: per-handler, explicit, easy to vary
- tradeoff: more repetition, easier to drift per service
Recommendation: B for the 15-service spread, because per-service variation dominatesChoosing wrongly among approaches is the most expensive error class in software work, because the cost compounds across every file touched afterward. A migration that picks decorator-based over middleware-based integration, or synchronous over event-driven extraction, locks in infrastructure consequences that are painful to undo. Plan mode makes the choice explicit and rejectable before any of that cost is incurred. the tested material repeatedly frames "multiple valid approaches" as one of the four explicit plan-mode conditions alongside architectural decisions, multi-file scope, and required exploration.
Boundary. The boundary is genuine divergence of approach, not cosmetic variation. If there is one obviously correct way to satisfy the requirement, multiple approaches is not triggered. The nearby opposite case is a mechanical edit where the "approach" is fixed by the requirement itself, such as wrapping a known parameter with a provided sanitization function; there the only variation is incidental and planning adds nothing.
Recurring specifics. the tested material uses pairs: middleware-based versus decorator-based auth integration, synchronous internal API versus event-driven queue for payments extraction, two re-sharding strategies behind revenue-recognition views. In each, the choice "ripples across foreign keys, materialized views, and downstream ETL jobs", which is the tell that the fork is architectural and not cosmetic.
Proposal. start direct and let the natural approach emerge from edits.
Why it attracts. emergent-design rhetoric.
Why it fails. the fork is known up front, so "emergence" is just deferred commitment that risks rework.
When it would be right. never, when the approaches are already named in the task.
- A mutation adds "a junior suggests just editing files and adjusting as issues come up"; the fix is that adjusting mid-stream is exactly the rework plan mode prevents. Another mutation offers "skip exploration, provide the exact target structure" as if specificity removes the need to verify dependencies; the fix is that verification is independent of how precisely the human described the goal.
Architectural decisions and service boundaries require plan mode
Tasks that force decisions about service boundaries, module dependencies, API contracts, or communication patterns have downstream consequences across many files. Plan mode surfaces those decisions explicitly so the human can evaluate and approve before implementation locks anything in. the tested material frames monolith-to-microservice restructuring, authentication-module extraction, and payments-service separation as canonical architectural work.
Architectural decisions that push a task into plan mode:
- where to draw a service boundary
- how to handle shared data between extracted services
- which communication pattern (sync API vs event queue)
- how to sequence a multi-package migration
- transaction-boundary handling across the new seamArchitectural choices are the skeleton the rest of the change hangs on. Getting a boundary wrong at file 15 of 60 means re-doing the first 15 plus reconciling everything built on the wrong seam. Plan mode catches the error at design time, where the cost is a rewritten paragraph rather than a rewritten subsystem. the tested material states plainly that architectural decisions are one of the four explicit plan-mode conditions.
Boundary. The boundary is whether the task forces a structural choice with cross-file consequences. A single-file change that happens to live inside an architectural component does not inherit the need to plan that component as a whole. The nearby opposite case is a config-value update inside a service that is architecturally sensitive but individually unambiguous; sensitivity of the code area does not by itself require planning when the specific edit is clear.
Recurring specifics. Boundaries named in the tested material include "service boundaries", "module dependencies", "transaction handling", "shared data handling", "communication patterns", and "migration sequencing". The payment-service extraction specifically names a "custom transaction wrapper" that direct execution missed, causing a half-migrated inconsistency.
Proposal. direct execution with detailed upfront instructions spelling out each service. Why.
Why it attracts. looks thorough.
Why it fails. the human cannot enumerate every dependency the agent will hit; direct execution still commits before discovery.
When it would be right. only if the structure is already fully known and fixed.
- Mutations split modes mid-task, e.g. "plan for the first five files, then direct for the rest"; the fix is that splitting loses the architectural context established during planning. Another offers "plan the first package, then edit as understanding forms"; the fix is that partial planning still bakes in early decisions before full boundaries are known.
Multi-file modifications need a settled strategy before any edit to avoid drift
When a change touches many files, applying it without a single agreed pattern produces inconsistent results: early files use one approach, later files use another, and a second pass is needed to reconcile them. Plan mode establishes one consistent pattern up front, so every file is converted the same way. the tested material frames this as preventing "pattern drift" across a codebase.
Drift failure signature (what the tested material shows after skipping plan mode):
files 01-08 converted with Pattern A
files 09-30 converted with Pattern B (incompatible with A)
-> required a second reconciliation pass
Root cause: no settled pattern before edits beganA migration is only correct if its rule is applied uniformly. Without a settled strategy, the agent makes local choices that vary as context shifts file to file, and the variation is exactly the kind that breaks compilation or behaviour. Plan mode commits the pattern before the first edit, making the subsequent direct-execution phase mechanical and consistent. the tested material's post-mortem item states plainly that executing directly "instead of first exploring the codebase in plan mode to settle on one pattern" is what caused early and late files to diverge.
Boundary. The boundary is whether uniformity across files matters. A one-file fix cannot drift. The nearby opposite case is a large set of truly independent mechanical edits, such as a codemod covering 240 tables where each swap is self-contained; those can go direct even inside a bigger migration, as long as the entangled minority is planned separately (see Rule 20).
Recurring specifics. Counts in the tested material range from 12 to 80 files, and the risk is described as "inconsistent code changes", "pattern drift", and "a second pass to reconcile". The fix is always to plan the pattern, then execute it uniformly.
Proposal. start 60 parallel sessions, one per file, in direct execution.
Why it attracts. speed.
Why it fails. parallel sessions cannot share context, so consistency is impossible.
When it would be right. never for a pattern-sensitive migration.
- The mutation embeds a prior failed attempt: "a prior run jumped straight to edits and needed rework after dependency conflicts emerged." The fix is to plan first. Another mutation frames a 25-service documentation refresh where inconsistency in terminology appears after three services; the fix is to plan the glossary and template before executing.
Codebase exploration requirement points to plan mode, often with the Explore subagent
When the task cannot be acted on until the agent understands dependencies, traces data flows, or maps existing structure, plan mode is the correct home because it permits that understanding to form without any write risk. For verbose discovery, the Explore subagent is dispatched to perform the read-only mapping in isolation and return only a summary, keeping the main context clean.
# Explore subagent as a read-only discovery pass before planning
# (invoked from within an interactive session; exact tool name verified against docs)
Task(subagent_type="Explore",
prompt="Map every call site of the deprecated util across all packages and report a dependency summary")
# The summary returns to the main context; the verbose file reads stay in the subagentExploration that changes nothing cannot do harm, and plan mode guarantees that. The Explore subagent adds context hygiene: a full dependency map across packages can be hundreds of files of listings and excerpts, and letting all of that sit in the main window degrades later responses. Isolating it preserves focus for the design and execution phases where retained context matters most. the tested material frames the Explore subagent as the right tool when "the location is unknown" or when "verbose discovery output would exhaust the context window".
Boundary. The boundary is whether discovery is needed and whether it is verbose. If the location is already known (a stack trace points at line 112), no exploration subagent is warranted and direct execution applies. The nearby opposite case is a small targeted fix where the relevant files are known; sending an Explore subagent there is the same overhead mistake as plan mode for a one-liner.
Recurring specifics. the tested material pairs discovery with plan mode: "use plan mode to explore the codebase, map all 12 module dependencies, identify circular dependencies, and evaluate extraction strategies". The Explore subagent is shown as the "read-only first pass to map class relationships" before a written plan. The signal phrase is "codebase is unfamiliar" or "need to understand structure before knowing what to change".
Proposal. delegate a one-line fix to a subagent to keep the main session clean.
Why it attracts. context-hygiene logic sounds right.
Why it fails. the overhead of spawning outweighs a one-line edit; the fix is direct execution.
When it would be right. only for verbose discovery, not trivial fixes.
- A mutation offers "delegate the removal decision itself to the Explore subagent while the main session performs the trace"; the fix is that the decision belongs in the main session and the trace (the verbose part) belongs in the subagent. Another mutation offers "Explore subagent to check other files for similar vulnerabilities before fixing one"; the fix is that checking elsewhere is a separate task from applying a fully specified fix.
A well-scoped single-file fix with a clear stack trace is direct execution
When a bug is localized to one function, identified by a precise stack trace, and the failure type is well understood, the agent can read the function, identify the faulty condition, and apply the fix without any exploration or design phase. Direct execution is the proportionate choice. Plan mode would produce a design document for a change that requires no design.
Direct-execution bug profile (from the tested material):
precise stack trace .... exact failure location is known
named function ......... the affected component is identified
specific bug type ...... off-by-one / null deref / missing validation
single file ............ no cross-file dependency to analyseThe whole point of plan mode is to resolve ambiguity before commitment. A single-function bug with a named failure type has no ambiguity: the location is known, the cause is known, and the correction is mechanical. Adding a planning round-trip spends time and context on a document that adds zero information. the tested material calls this "the textbook case for direct execution" and warns against treating a clear test failure as ambiguous.
Boundary. The boundary is the combination of single file, known location, and well-understood cause. If any one of those is missing, the task leans toward planning. The nearby opposite case is a "pagination" change offered as plan mode "because pagination is architectural"; the fix is that the specific off-by-one in one function is bounded, so the general architectural weight of pagination is irrelevant to that particular edit.
Recurring specifics. the tested material recurs on off-by-one errors in a single pagination function, null-pointer exceptions in one function, null-check conditionals, date-validation rules, and configuration value updates. Each is "single file", "clear stack trace", and "known cause". The phrasing "precise stack trace, a named function, a specific bug type, and a single file" is the canonical checklist.
Proposal. plan mode because the domain (e.g. pagination) is architectural.
Why it attracts. principled-sounding.
Why it fails. the specific bug is bounded; domain weight does not apply to a located fault.
When it would be right. if the change actually touched service boundaries, which it does not here.
- The mutation adds "the team's default is plan mode for everything"; the fix is that a uniform default misreads a clear failure as ambiguous. Another mutation offers "plan mode because unit tests can be misleading about the true cause"; the fix is that a precisely located failure is not made ambiguous by the mere possibility of misdirection.
Known fix, known location, known approach satisfies the direct-execution checklist
Direct execution is the default and most efficient mode whenever three conditions hold simultaneously: the change is specific, the location is known, and the approach is clear. the tested material states this as an explicit three-item checklist and notes that any "no" shifts the task toward plan mode. This rule generalises Rule 8 from bug fixes to any well-understood edit.
# The decision is the developer's; nothing auto-selects the mode
# Direct execution is chosen explicitly for a checklist that is all-yes:
# claude "add a null-check to helper validateUser in auth/helpers.ts line 42"The checklist is a decision filter for ambiguity. Specificity rules out vague intent; a known location rules out discovery; a clear approach rules out design. When all three are yes, the round-trip of planning buys nothing. the tested material frames direct execution as the mode for "well-defined, narrow-scope tasks where the context is localized to a single file or a very clear objective".
Boundary. The boundary is the failure of any one checklist item. A change that is specific and located but whose approach is unclear (three reasonable ways to add a feature) is no longer direct. The nearby opposite case is a fully specified security fix provided by a separate team: the approach is known (wrap the parameter with the supplied function), the location is known (line 112), and the change is specific (one line), so despite the security domain it is direct execution.
Recurring specifics. the tested material lists concrete direct-execution edits: adding a null check, adding a validation conditional, adding one line of logging to a visible function, updating a configuration value, and applying a provided sanitization function to one line. The checklist is restated as "(1) change is specific, (2) location is known, (3) approach is clear".
Proposal. plan mode for literally everything, always.
Why it attracts. consistency.
Why it fails. uniform planning adds overhead to clear fixes.
When it would be right. never as a blanket rule.
- The mutation casts the same edit inside a CI review context; the fix is that the execution environment does not change the checklist result. Another mutation wraps it in "the team lead is unsure whether to use plan or direct"; the fix is to apply the checklist rather than defer to environment sensitivity.
The hybrid pattern is plan THEN execute, not plan OR execute
For large migrations and architectural work, the tested pattern is two sequential phases: plan mode designs the strategy, then direct execution applies it. the tested material emphasises "plan THEN direct, not plan OR direct". The design phase settles the pattern and the approach; the execution phase applies them file by file with the strategy already decided.
Hybrid: logging-library migration across 30 files
Plan phase (read-only):
- list every file importing old-library
- map API differences old -> new
- design one migration pattern
- enumerate edge cases
Execute phase (direct):
- apply the pattern to each file in the planned order
- result is consistent across all 30 filesThe hybrid captures the benefit of both modes. Planning resolves ambiguity without risk; direct execution then applies a known plan efficiently without re-deliberating each file. the tested material shows the failure of inverting the order (direct first, plan after) as "backwards", and the failure of planning the mechanical edits as "overhead". The pattern is the intended resolution of the plan-versus-direct tension for big tasks.
Boundary. The boundary is task size and ambiguity. For a trivial edit, the hybrid collapses to direct execution alone (no planning phase needed). For a genuinely ambiguous big task, both phases are required. The nearby opposite case is a 50-file migration where someone proposes "plan mode for the whole duration including every edit"; the fix is that plan mode cannot edit, so execution must leave it.
Recurring specifics. the tested material's worked example is a logging-library migration across 30 files: plan identifies every file importing the old library, maps API differences, designs the migration pattern, and checks edge cases; execute applies the pattern file by file. The CLI framing is claude --permission-mode plan for the plan phase, then a normal session to execute.
Proposal. plan mode for the full duration including every edit.
Why it attracts. keeps oversight.
Why it fails. plan mode is read-only, so the edits can never happen in it.
When it would be right. never; execution must leave plan mode.
- A mutation offers "remain in plan mode for the entire migration to preserve exploration context"; the fix is that plan mode cannot implement, so preservation of context must happen via the plan artifact, not by staying read-only. Another offers "restart plan mode from scratch to re-verify before executing"; the fix is that re-verifying an already-approved plan is wasted work.
Read-only exploration guarantees safe human review before any write lands
Because plan mode blocks writes, the human can read the full proposal, the intended file set, and the ordered changes before granting any write access. This review gate is what makes plan mode a governance tool, not merely a thinking aid. the tested material frames plan mode as the feature that "lets you review the diff-like proposal before approving" and "review a proposed multi-file refactor before edits are applied".
# Inside plan mode, review the intended changes, then release writes
/plan # shows the proposed file set and edits (no writes applied)
/approve # transitions to execution, writes now permittedA write that has already happened cannot be cheaply unwound if the strategy was wrong. By blocking writes until approval, plan mode converts a potentially destructive action into a previewable one. The human sees "which files, what changes, in what order" and can course-correct on a large or hard-to-reverse change before it is executed. the tested material names this as a "tested skill" of the platform.
Boundary. The boundary is write access. In direct execution the human reviews after the fact via a diff and revert, which is fine for small reversible changes but risky for large cross-cutting ones. The nearby opposite case is a one-file fix where post-hoc diff review is proportionate; there the absence of a pre-write gate is acceptable because the blast radius is tiny.
Recurring specifics. the tested material shows /plan (while in plan mode) as the command that surfaces the proposed changes for review, and /approve as the action that releases write access. The proposal is described as "diff-like" because it previews intended edits without applying them.
Proposal. plan mode prevents the human from ever reviewing changes.
Why it attracts. inverts the feature.
Why it fails. review is the entire purpose.
When it would be right. never.
- A mutation offers a CI context where "plan mode should always be used to prevent unintended modifications during automated runs"; the fix is that CI safety is not the purpose of plan mode and direct execution remains correct for well-scoped CI fixes. Another mutation frames review as possible only through
/review; the fix is that/planin plan mode is the review surface.
The Explore subagent isolates verbose discovery output for context hygiene
On multi-phase tasks, the discovery phase can flood the main conversation with file listings, dependency graphs, code excerpts, and analysis notes. The Explore subagent runs that discovery in isolation, returns a concise summary, and keeps the verbose intermediate output out of the main context window. The main session stays focused for the design and implementation phases where retained context is most valuable.
Explore subagent contract (from the tested material):
runs discovery in a separate context
produces a summary of findings
returns only the summary to the main conversation
keeps verbose reads/excerpts out of the main windowModel quality degrades as the context window fills with low-signal volume. Verbose discovery is exactly that kind of volume: necessary to produce, but not necessary to keep. Isolating it means the main agent sees the conclusion (the dependency map) without the raw hundreds of file reads. the tested material frames this as the primary reason to use the Explore subagent, distinct from any speed gain.
Boundary. The boundary is discovery verbosity and the need to keep the main context clean. For a small, low-volume trace, doing it inline is fine. The nearby opposite case is a trivial fix where an Explore subagent would add coordination cost for no hygiene benefit; there the right move is direct execution, not a subagent.
Recurring specifics. the tested material describes the Explore subagent as running "in isolation", producing "summaries of its findings", and returning those summaries "to the main conversation". The failure mode it prevents is the main context filling with "file listings, dependency graphs, code excerpts, analysis notes" so that "later responses" degrade.
Proposal. use the Explore subagent for speed.
Why it attracts. plausible.
Why it fails. the tested material's stated reason is context hygiene, not wall-clock speed.
When it would be right. speed can be a side benefit, but it is not the tested rationale.
- A mutation offers "do all phases in the main conversation with periodic
/compact"; the fix is that compaction does not match the isolation benefit. Another offers "switch to headless mode with--continuepassing summaries between batch calls"; the fix is that this addresses a different problem (async batching) and is not the context-isolation mechanism.
Subagent context is not inherited; hand task-scoped findings into the spawn prompt
When a coordinator spawns a subagent, the subagent starts in a fresh conversation. It does not inherit the parent's running history, prior tool outputs, or other subagents' rationales. The only thing that reaches the subagent is what the spawn prompt explicitly includes. Therefore the human or coordinator must place the just-enough, task-scoped prior findings directly into that prompt rather than assuming the subagent can read surrounding context.
Correct subagent spawn prompt (per the tested material):
- the specific conversion decisions for THIS module
- the originating agent's rationale for those decisions
- the original and migrated code
(NOT the whole transcript, NOT a file the subagent must find itself)Isolation is the feature that makes subagents useful for context hygiene (Rule 12): their intermediate reads never pollute the parent. But isolation cuts both ways; the subagent also cannot see what the parent learned unless told. the tested material shows a validation subagent that judged modules "in apparent isolation" because it was expected to pull the coordinator's conversion-decision history on its own, and it could not. Handing the specific decisions and the originating rationale directly into the spawn prompt fixed the behaviour.
Boundary. The boundary is what the spawn prompt carries. A subagent that receives the relevant decisions, the originating agent's rationale, and the artifacts under review judges correctly. A subagent handed the entire transcript, or pointed at a scratchpad file it is expected to consult, does not improve and may even regress. The nearby opposite case is fork_session, which does branch from a baseline that contains prior history; that is the right tool when shared history is actually wanted, unlike a fresh spawn.
Recurring specifics. the tested material frames the correct spawn as: include "directly in that subagent's prompt the specific conversion decisions governing the module under review and the originating migration_agent's rationale for it, alongside the original and migrated code". The wrong variants are "the entire accumulated conversation transcript" and "a shared scratchpad the validator consults on its own".
Proposal. fork the session so the validator inherits full history.
Why it attracts. seems to share context.
Why it fails. forking is heavier and the tested material's fix was the scoped prompt, not a fork.
When it would be right. when truly shared evolving history is needed across the run.
- A mutation adds a coordinator prompt line "ensure validators apply the project's established conversion decisions" and notes the false-judgment rate only dropped from 19% to 14%; the fix is that a vague instruction does not substitute for placing the decisions in the spawn prompt.
Executing architectural work directly causes costly mid-implementation rework
When architectural or multi-approach work is started in direct execution, the agent commits to early decisions before it has seen the whole dependency picture. Dependencies that surface at file 40 force a reversal of work already done at files 1 to 39. the tested material frames this as "costly rework", "two days of rework", and "half-migrated inconsistency".
Direct-execution architectural failure (recurring shape):
move files 01-08 with Approach A
at file 09 discover a circular dependency / custom wrapper
Approach A is now invalid for 01-08
-> reverse 01-08 and redo with Approach BDirect execution optimises for acting on the current best understanding. For unambiguous work that is ideal. For architectural work the current understanding is incomplete by definition until exploration finishes, so acting early bakes in wrong seams. Plan mode moves the discovery ahead of the commitment, so the reversal happens on paper instead of in code.
Boundary. The boundary is whether wrong early decisions are expensive to undo. For a one-line fix they are not, so direct execution is correct. For a 40-file extraction with a custom transaction wrapper they are, so plan mode is correct. The nearby opposite case is the "start direct, switch to plan when complexity emerges" trap; the fix is that the complexity is already known from the requirements.
Recurring specifics. the tested material's concrete failures: a payment-service migration "realises the legacy service uses a custom transaction wrapper it did not account for, and the partial migration is now inconsistent"; a prior monolith extraction "required two days of rework once a circular import surfaced midstream"; a notification extraction "breaks circular dependencies with the user-profile module" after eight files moved.
Proposal. start direct, switch to plan only if complexity appears.
Why it attracts. seems responsive.
Why it fails. the complexity is stated up front; switching later is too late.
When it would be right. never for stated architectural scope.
- A mutation adds "a similar refactor last quarter was started this way and needed two days of rework"; the fix is to plan first this time. Another mutation offers "break into four groups of ten and test after each"; the fix is that grouping does not replace settling the approach.
Planning trivial work adds overhead with zero design value
Applying plan mode to a clear, located, single-step edit produces a design document for a change that requires no design. The round-trip consumes time and context and adds no information. the tested material frames this as "overhead without benefit" and warns against treating a clear failure as ambiguous.
Overhead trap (what the tested material rejects):
plan mode for a one-line null check
plan mode for a one-line logging addition
plan mode "because any production change carries architectural risk"
-> all marked as overhead without benefitPlan mode's value is resolving ambiguity. Where ambiguity is absent, the plan restates what is already known. A one-line null check or a one-line logging addition has a known location and known approach, so the plan is pure ceremony. Matching rigour to risk and scope is the governing principle: plan mode's value is highest for large, cross-cutting, high-risk changes, not for small fixes.
Boundary. The boundary is genuine simplicity. A change that looks small but hides an architectural fork is not trivial and may need planning. The nearby opposite case is the security fix offered as plan mode "because security has architectural implications"; the fix is that the specific fix is fully specified and urgent, so planning is the wrong overhead there.
Recurring specifics. the tested material lists "plan mode for literally everything, always" as an explicit wrong answer, and "keep using plan mode so every change is documented consistently" as a wrong guidance. The correct guidance is direct execution for "a single null check in one helper function, identified precisely by a failing test".
Proposal. plan everything for documentation consistency.
Why it attracts. audit trail.
Why it fails. consistency does not justify the round-trip on clear fixes.
When it would be right. never as a blanket default.
- The mutation sets "the team's default is plan mode for everything"; the fix is to override the default for the clear fix. Another mutation offers "plan mode because the team wants every change documented"; the fix is that documentation is not the purpose of plan mode.
File count is a heuristic signal, not the decision criterion
The number of files a task touches is a useful clue but not the deciding factor. A ten-file, well-scoped, clear-approach change can be direct execution, while a two-file change with a genuine fork can need plan mode. the tested material states this explicitly: "File count is a heuristic indicator of complexity but not the actual criterion."
File-count reasoning (from the tested material):
small + ambiguous ...... plan mode (the ambiguity, not the size)
large + mechanical ..... direct execution may be fine (the mechanicality, not the size)
large + ambiguous ...... plan mode (both size and ambiguity align)File count correlates with the things that actually matter (exploration need, approach diversity, architectural consequence) but does not equal them. A broad mechanical sweep with no design fork is safe to execute directly even if large; a tiny change that forces a structural choice is not. Anchoring on count produces both false positives (over-planning big mechanical work) and false negatives (under-planning small architectural work).
Boundary. The boundary is whether the count is standing in for real ambiguity. When the many files share one mechanical rule, count is a false trigger. When the few files hide a fork, count is a false all-clear. The nearby opposite case is a 30-file migration that does need planning; there the count happens to align with real ambiguity, but the reason is the ambiguity, not the number.
Recurring specifics. the tested material pairs a "10-file but well-scoped change" as direct execution against a "30-file migration" as plan mode, and clarifies the 30-file case is planned "because a consistent migration strategy is needed", not merely because of the count.
Proposal. plan for multiple files, direct for single.
Why it attracts. simple rule.
Why it fails. ignores approach ambiguity on small changes.
When it would be right. only as a rough first guess.
- A mutation offers "mode selection based on file count: plan for multiple files, direct for single"; the fix is that this heuristic is explicitly rejected as the criterion.
A sensitive, urgent, or CI context does not by itself force plan mode
Plan mode is selected for ambiguity and architectural consequence, not for the sensitivity of the code area or the execution environment. A fully specified, urgent, single-line security fix is direct execution despite the security domain. A well-scoped fix inside a CI review is direct execution despite the automated environment. the tested material warns against "applying plan mode to all database-related changes regardless of scope" as an anti-pattern.
Sensitivity does not force plan mode:
urgent + single line + fully specified -> direct execution
CI + clear stack trace + small scope -> direct execution
only the domain being "security/database/CI" -> not sufficient reason to planMode choice protects against ambiguity-driven rework, not against the reputational cost of a bad edit. A sensitive edit that is already fully specified carries no open decision to resolve, so planning adds delay without reducing risk. The right place for sensitivity controls is the permission system (Rules 23 and 24), not the planning mode. Using plan mode as a blanket CI safety mechanism "misunderstands its purpose entirely".
Boundary. The boundary is specification, not sensitivity. An urgent fix that is actually ambiguous (the right approach among several is unclear) still warrants planning even under time pressure. The nearby opposite case is the SQL-injection fix: "urgent" plus "isolated to a single line" plus "fully understood" makes direct execution correct, and the urgency argues against adding round-trips.
Recurring specifics. the tested material's security item gives three qualifiers: "fix is fully understood", "urgent", and "isolated to a single line (line 112, userInput parameter)". All three together make direct execution the only logical choice. The CI item gives "clear stack trace", "well-understood fix", and "small scope (one config file and one service class)".
Proposal. plan mode because security issues have architectural implications.
Why it attracts. generally true.
Why it fails. the specific fix is already fully defined by the security team.
When it would be right. when the security fix itself is ambiguous in approach.
- A mutation offers "use an Explore subagent first to map the full subsystem even for a small fix"; the fix is that known relevant files make the exploration unnecessary. Another offers "plan mode as a general CI safety mechanism"; the fix is that this misreads the mode's purpose.
Recognize complexity upfront instead of waiting for surprises to switch modes
When the task description already states the work is complex (monolith restructuring, 60-file microservice split, multi-approach extraction), plan mode should be chosen immediately. Waiting until complexity "emerges" during direct execution is the wrong move because the complexity was never speculative; it was in the requirements from the start.
Upfront-complexity rule:
requirements name architecture + many files -> plan mode immediately
requirements name a clear single fix -> direct execution
complexity appears only after a small start -> switching then is fine (not the trap)The trap is treating plan mode as a reaction to discovered difficulty rather than a proactive choice for stated difficulty. If the requirements name architectural decisions and many files, the exploration should happen before any commit. Switching to plan mode after eight files are already moved means the early files may be on the wrong approach. the tested material repeats this as a distinct exam trap: "the complexity is known, not speculative. Do not wait for surprises."
Boundary. The boundary is whether complexity is already evident in the prompt. If it is, plan up front. If the task looked simple but a genuine fork appears mid-work, then switching to plan mode is appropriate and is not the trap; the trap is only when the complexity was foreseeable. The nearby opposite case is a task that genuinely reveals an unexpected architectural issue after a small start; there, switching is correct because the complexity was not stated.
Recurring specifics. the tested material's phrasing: "When the requirements already say the task is complex (e.g. 'restructure the monolith into microservices'), reach for plan mode straight away." The anti-pattern is "start in direct execution and switch to plan mode only if unexpected complexity appears."
Proposal. start direct, switch to plan if complexity emerges.
Why it attracts. seems incremental.
Why it fails. the complexity was stated, so emerging is too late.
When it would be right. only when complexity was genuinely unforeseen.
- A mutation offers "plan the first five files, then direct for the rest"; the fix is that partial upfront planning still commits early before full boundaries are known. Another offers "explore the first package, then edit as understanding forms"; the fix is that this is the wait-for-surprises trap in disguise.
The mode choice is the developer's explicit decision; the agent does not auto-select
Plan mode versus direct execution must be specified by the developer. The agent does not infer task complexity and does not automatically switch modes. the tested material states this twice as an explicit single-select answer: "Plan vs direct execution is developer decision not automatic; Claude Code doesn't infer task complexity."
# The developer, not the model, selects the mode
Shift+Tab Shift+Tab # developer cycles into plan mode
/plan # developer invokes plan mode
claude --plan "..." # developer starts in plan mode
# there is no automatic inference of complexity by the agentAutomatic mode selection would require the agent to judge ambiguity from the prompt, which is exactly the human's call to make given risk tolerance and review needs. The product keeps the human in the loop for that decision. This also means there is no "complexity threshold configured in CLAUDE.md for automatic mode selection"; such a feature is explicitly marked wrong in the tested material.
Boundary. The boundary is the human action: pressing Shift+Tab, typing /plan, or passing --permission-mode plan chooses plan mode; otherwise the session is in normal (direct) mode. The nearby opposite case is a custom slash command that carries a plan_mode frontmatter flag (Rule 25); even there the mode is set by configuration the developer wrote, not by the agent inferring.
Recurring specifics. the tested material rejects: "Claude Code analyzes task complexity and selects mode automatically", "Claude Code defaults to plan mode for all tasks unless overridden", and "complexity threshold is configurable in CLAUDE.md for automatic mode selection". The correct answer is always that the developer chooses.
Proposal. the agent auto-selects based on complexity.
Why it attracts. convenience.
Why it fails. explicitly false in the tested material.
When it would be right. never in the tested product.
- A mutation asks whether CI environments force plan mode; the fix is that the environment does not change the human's explicit choice. Another asks whether plan mode is "always required"; the fix is no, it is a developer-selected option.
Route by task shape: mechanical edits direct, entangled edits plan, inside one migration
A single large migration often contains both mechanical, self-contained edits and entangled edits with multiple valid approaches and cross-cutting consequences. the tested material's most sophisticated item routes by task shape: the mechanical majority goes straight to direct execution, while the entangled minority goes into plan mode, where the plan adapts as dependencies surface.
Routing by task shape (300-table billing migration):
~240 mechanical column swaps .... direct execution (codemod already covers)
~60 entangled re-sharding tables . plan mode, adaptive subtasks as deps surfaceApplying one mode to the whole migration misallocates effort: planning 240 self-contained column swaps is pure overhead, while executing 60 entangled revenue-recognition tables directly invites rollback churn. Routing by shape matches mode to ambiguity per sub-task. The entangled plan mode also "generates re-sharding subtasks adaptively as each newly inspected table reveals which downstream objects it touches", which is the adaptive decomposition of Rule 30.
Boundary. The boundary is whether a given sub-task has a settled rule or a live fork. Mechanical codemod swaps are settled; revenue-recognition re-sharding with foreign-key and ETL ripple is not. The nearby opposite case is planning the entire 300-table set up front as one fixed sequence; the fix is that the mechanical tables do not need it and the entangled ones benefit from adaptive, not fixed, decomposition.
Recurring specifics. the tested material splits roughly 240 mechanical "column-type swap" tables from about 60 entangled "revenue-recognition" tables behind views. The correct routing sends the 240 to direct execution and the 60 into plan mode with adaptive subtask generation.
Proposal. plan all 300 tables up front as one fixed sequence.
Why it attracts. uniform.
Why it fails. overhead on mechanical tables, rigidity on entangled ones.
When it would be right. never.
- A mutation offers "process tables top to bottom in the planned order" for all; the fix is that the entangled ones need adaptive decomposition, not a fixed order.
Plan mode establishes a consistent strategy and glossary for multi-file content updates
When a change touches many files that must read consistently (documentation, architecture guides, terminology), plan mode produces an explicit strategy: a terminology glossary, a section template, and a validation criterion, applied uniformly across every file. Skipping this produces the inconsistency the tested material shows after three services were updated with mixed terminology.
Content-update plan artifact:
- terminology glossary (old term -> new term)
- section-by-section template
- validation criterion (no old-platform mention remains)Content consistency is a pattern problem like code consistency (Rule 6); the rule must be settled before edits. Without a glossary and template, each file is written from local context and drifts. Plan mode commits the shared vocabulary and structure up front, so the execution phase is uniform. the tested material's 25-service documentation refresh fails exactly because "some guides still mention the old platform, others use inconsistent terminology".
Boundary. The boundary is whether uniformity of voice and terms matters. A single doc edit needs no glossary. The nearby opposite case is a code migration where the "pattern" is API usage rather than prose; the same plan-mode logic applies, just with a code pattern instead of a glossary.
Recurring specifics. the tested material's correct answer: "use plan mode to establish a consistent update strategy, terminology glossary, and section-by-section template before executing changes across all 25 services". The failure mode is inconsistency after three services.
Proposal. do it in one continuous session to keep consistency via context.
Why it attracts. context continuity.
Why it fails. a 25-service session exhausts context and still drifts.
When it would be right. never for that size.
- A mutation offers few-shot examples (Rule 32) as the fix for inconsistent config transforms; the fix is that few-shot helps strict formatting but the 25-guide task needs a plan-level glossary, not just examples.
Plan mode previews a proposed refactor as a diff before anything is applied
A core use of plan mode is to let the human review the intended approach on a large or hard-to-reverse change before execution. The agent proposes which files, what changes, and in what order, and the human can adjust before any edit lands. This is framed in the tested material as one of the two appropriate uses of plan mode: "reviewing a proposed multi-file refactor before edits are applied" and "letting Claude outline a migration strategy you can approve or adjust".
Plan mode is for (per the tested material, two correct uses):
- reviewing a proposed multi-file refactor before edits are applied
- outlining a migration strategy the human can approve or adjust
Plan mode is NOT for:
- bypassing permission prompts
- disabling toolsLarge cross-cutting changes are expensive to reverse once applied, so a preview is worth a round-trip. Plan mode's read-only guarantee (Rule 1) makes the preview honest: the proposal cannot silently edit. the tested material names this as a "directly tested skill" of the platform, distinguishing it from modes that bypass permissions.
Boundary. The boundary is reversibility and size. A one-file fix is cheap to revert, so post-hoc diff review is fine and plan mode is overhead. A 15-file refactor is not cheap to revert, so preview-then-approve is the proportionate rigour. The nearby opposite case is the security fix where urgency overrides the preview; there the edit is both small and fully specified, so the preview round-trip is the wrong cost.
Recurring specifics. the tested material's appropriate plan-mode uses are "reviewing a proposed multi-file refactor before edits are applied" and "letting Claude outline a migration strategy you can approve or adjust". The wrong associations are "bypassing all tool permission prompts" and "permanently disabling Bash".
Proposal. plan mode to bypass permission prompts.
Why it attracts. conflates review with bypass.
Why it fails. plan mode is read-only design, not prompt skipping.
When it would be right. never.
- A mutation claims "this isn't possible; large refactors must be done manually"; the fix is that plan mode explicitly supports previewing the approach before execution.
`allowedTools` gives deterministic, tool-level permission scoping
allowedTools is a session-level control that restricts which tools are available and can scope them. It is enforced by the local CLI engine, so it is deterministic rather than advisory. the tested material's correct answer for "ensure Claude Code cannot accidentally delete production config or modify the migration directory" is to configure allowedTools to limit the session to Read, Grep, Glob, and Write for files within the payment module directory.
# Restrict a session to read-oriented tools plus writes scoped to one directory
claude --allowedTools "Read" "Grep" "Glob" "Write(payments/**)"
# exact tuple syntax should be verified against current CLI docsUnlike a CLAUDE.md instruction, which relies on the model's compliance, allowedTools is a hard gate: if a tool or a scoped path is not on the list, the call is blocked. This makes it the right control when the requirement is "cannot modify production config" rather than "prefer not to". the tested material contrasts it directly with the CLAUDE.md alternative, marking the advisory instruction as not deterministic.
Boundary. The boundary is the tool surface. allowedTools controls which tools run and where; it is separate from plan mode's read-only guarantee and from the permissions.deny list (Rule 24). The nearby opposite case is using CLAUDE.md to say "never modify config/ or migrations/"; the fix is that this is advisory only and can be overridden by the model, so it fails the "cannot accidentally" bar.
Recurring specifics. the tested material scopes the session to "only Read, Grep, Glob, and Write for files within the payment module directory". The headline: "allowedTools provides tool-level permission control that can restrict which tools are available and their scope."
Proposal. add a CLAUDE.md rule "never modify config/ or migrations/".
Why it attracts. easy.
Why it fails. advisory, not deterministic; the model may comply or not.
When it would be right. as a soft preference, never as a hard block.
- A mutation offers
allowedToolsto restrict to read tools plusWritescoped to the module; the fix is that scoping the write path is what enforces the boundary.
`permissions.deny` in `.claude/settings.json` enforces a physical Deny to Ask to Allow order
Project settings under .claude/settings.json carry a permissions block with a strict evaluation order: Deny, then Ask, then Allow. If a path or tool matches a rule in the deny array, the CLI engine physically blocks the call even if the user prompts for it. Combined with plan mode, this gives maximum oversight for sensitive sessions.
{
"permissions": {
"deny": [
"Read(.env)",
"Read(.env.*)"
]
}
}The deny list is the strongest guarantee available short of changing the filesystem itself, because it is enforced by the engine, not the model. the tested material frames secret protection as: extract secrets into environment variables, add sensitive patterns to permissions.deny (for example Read(.env), Read(.env.*)), and run in plan mode for sensitive sessions. The hierarchy is what makes deny win over any later allow or over a user prompt.
Boundary. The boundary is the deny rule match. A Read on .env matching Read(.env.*) is blocked regardless of intent. The nearby opposite case is allowedTools pre-approval (Rule 23 and 27), which grants approval rather than restricting the surface; the two solve different problems (block specific paths versus pre-approve specific tools).
Recurring specifics. the tested material's exact pattern: Read(.env) and Read(.env.*) inside the deny array. The hierarchy is "Deny to Ask to Allow". The reference is the settings permissions documentation.
Proposal. a CLAUDE.md rule never to read .env.
Why it attracts. simple.
Why it fails. advisory, and a user prompt can talk the model past it.
When it would be right. never as a hard block.
- A mutation offers "store keys in a separate
.envand add aCLAUDE.mdrule"; the fix is that the deny list is the deterministic control, with env-var extraction as the hygiene step.
A custom slash command with no `plan_mode` frontmatter inherits the session mode
A slash command defined in a markdown file with YAML frontmatter can set plan_mode: true to force plan mode for that command. If the frontmatter omits plan_mode, the command follows whatever mode the session is already in. So a command run inside a session started with --plan will propose a plan first; the same command in a normal session runs directly.
---
description: Propose a refactor of the auth module
plan_mode: true
---
# command body: Claude will propose a plan first, then await approvalThe frontmatter flag is an explicit override of the default inheritance. When absent, the command should not silently change the mode the human chose for the session, because mode is the human's decision (Rule 19). the tested material's correct answer: "If plan_mode is not set in the command's YAML frontmatter, the command follows whatever mode the agent is already in."
Boundary. The boundary is the presence of the plan_mode key. With it set, the command forces that mode regardless of session. Without it, inheritance applies. The nearby opposite case is assuming a command "always runs without plan mode" or "always requires plan mode"; the fix is that absence means inherit.
Recurring specifics. the tested material: "the command inherits the session's current mode (interactive or plan)". The flag name is plan_mode in the frontmatter. The CLI shorthand --plan sets the starting session mode that the command then inherits.
Proposal. plan mode is always required for the command.
Why it attracts. assumes safety.
Why it fails. only if plan_mode: true is set.
When it would be right. when the frontmatter sets it.
- A mutation pairs this with
claude --planso the command proposes a plan; the fix is that the inheritance comes from the session flag, not the command.
Headless `-p` runs non-interactively; plan mode and `/resume` do not apply to it
The -p (print) flag runs Claude Code non-interactively and prints the result, making it suitable for scripts, CI/CD, and automation. In this mode, interactive features such as plan mode and /resume do not apply, because there is no human in the loop to approve a plan or resume a session. the tested material marks "interactive features like plan mode or /resume don't apply to unattended runs".
# Unattended, non-interactive run; plan mode and /resume do not apply
claude -p "migrate module payments/order.ts to the new client signature"Unattended runs must terminate and produce output without waiting for approval. Plan mode's value depends on a human reviewing and approving; with no human, that gate is meaningless, so the run is effectively direct execution governed by pre-approved permissions. the tested material frames -p as following the Unix philosophy: pipeable, chainable, invocable from scripts.
Boundary. The boundary is interactivity. An interactive session can use plan mode and /resume; a -p run cannot. The nearby opposite case is a -p run that still needs oversight; there the oversight is achieved via --allowedTools pre-approval (Rule 27), not via plan mode's interactive gate.
Recurring specifics. the tested material: "Claude Code's headless mode (claude -p "...") runs non-interactively and prints results". The flag is -p. Interactive plan mode and /resume are explicitly out of scope for it.
Proposal. -p disables safety checks.
Why it attracts. fear of headless.
Why it fails. the tested material says it only skips interactive features, not safety; permissions still apply.
When it would be right. never as stated.
- A mutation offers headless
-pto "prevent interactive file edits until re-enabled"; the fix is that-pis non-interactive by design, not a toggle for interactivity.
`--allowedTools` pre-approves tools for unattended fan-out; it does not restrict the surface
In headless fan-out, a script loops over a task list and invokes claude -p once per item. Each invocation needs its tools approved without a human prompt, so --allowedTools pre-approves the listed tools. the tested material is explicit that --allowedTools grants approval rather than restricting the overall tool surface; restriction is a separate concern handled by allowedTools scoping or permissions.deny.
# Fan-out over 1800 modules, each a fresh context, tools pre-approved
for f in $(cat modules.txt); do
claude -p "migrate $f to the new ORM signature" --allowedTools "Read" "Edit" "Bash(git:*)"
done
# refine the prompt on the first 2-3 files before launching the full batchUnattended runs block on permission prompts, so the tools they need must be pre-approved. Because each claude -p starts with a fresh context window, fan-out also sidesteps context accumulation, which is the real reason to loop rather than run one giant session. the tested material's pattern: generate a task list, loop calling claude -p per file, refine the prompt on the first two or three files before the full batch.
Boundary. The boundary is approval versus restriction. --allowedTools lets listed tools run without prompts; it does not shrink what the agent could otherwise do. To restrict, scope the allowedTools entries or add permissions.deny rules. The nearby opposite case is assuming --allowedTools "locks down" the agent; the fix is that it opens the listed tools, and lockdown needs the other controls.
Recurring specifics. the tested material's fan-out: "a scripted loop over the file list that invokes claude -p once per module, with --allowedTools pre-approving the required tools". It notes pre-approval "grants approval rather than restricting Claude's overall tool surface".
Proposal. one interactive session for the whole batch.
Why it attracts. continuity.
Why it fails. concentrates the job into one context window and needs a human.
When it would be right. never for large mechanical fan-out.
- A mutation offers repeating a prompt with
/loop; the fix is that/loopstays in one session and lacks scriptability, so the fan-out pattern prefers per-fileclaude -p.
Subagent definitions via the SDK `agents` parameter run in a fresh isolated context
In the Claude Agent SDK, a subagent is defined through the agents parameter. Each such subagent runs in its own fresh conversation: its intermediate tool calls and file contents stay inside the subagent's context, and only its final message returns to the parent. The parent invokes subagents through the Agent tool, so including Agent in allowedTools auto-approves the delegation in unattended runs.
{
"agents": {
"security_reviewer": {
"tools": ["Read", "Grep", "Glob"],
"prompt": "Review the given files for injection risks and return a concise report."
}
},
"mcpServers": {
"playwright": { "command": "npx", "args": ["-y", "@playwright/mcp@latest"] }
},
"allowedTools": ["Agent", "mcp__playwright__*"]
}Isolation is what makes delegation safe for context hygiene (Rules 12 and 13): a read-heavy review that would flood the main window instead stays contained, and only the conclusion returns. The SDK model also matches the core finding that subagent context is not inherited (Rule 13); the parent must hand task-scoped findings into the spawn prompt. the tested material pairs this with MCP server connection: a local stdio server is managed via the SDK's mcpServers option and its tools are allowlisted in allowedTools.
Boundary. The boundary is transport and context model. A remote, HTTP-exposed MCP server can be wired through the Messages API mcp_servers plus an mcp_toolset entry; a local stdio server needs the SDK's mcpServers because the application must spawn and manage it. The nearby opposite case is doing the review directly in the main agent; the fix is that this pulls every read into the primary context and degrades focus.
Recurring specifics. the tested material: "A subagent defined through the SDK's agents parameter runs in its own fresh conversation"; "Claude invokes subagents through the Agent tool, so including it in allowedTools auto-approves the delegation." For a local Playwright server: register it in mcpServers and allow mcp__playwright__* in allowedTools.
Proposal. attach the local stdio server through the Messages API mcp_servers only.
Why it attracts. uniform MCP wiring.
Why it fails. the connector requires an HTTP-exposed server; local stdio needs the SDK-managed mcpServers.
When it would be right. for a publicly exposed HTTP server.
- A mutation offers the MCP connector for a local stdio server; the fix is the transport mismatch. Another offers the review in the main loop; the fix is the context-pollution cost.
Apply least privilege: give a read-heavy reviewer subagent only read-oriented tools
When a subagent's job is review or investigation, it should be granted only the tools it needs, which for a read-heavy step means read-oriented tools and no write tools. the tested material frames this as applying least privilege to "a step that never needs to modify anything". Combining Rule 28's isolation with read-only tool scoping yields both context hygiene and a minimal blast radius.
{
"agents": {
"security_reviewer": {
"tools": ["Read", "Grep", "Glob"]
}
},
"allowedTools": ["Agent"]
}A reviewer that cannot write cannot accidentally mutate the codebase it is auditing, and a narrower tool list reduces the chance of an off-target action. the tested material's correct subagent definition includes "read-only tools" and notes the benefit explicitly. This is the same determinism principle as allowedTools (Rule 23) but applied at the subagent boundary.
Boundary. The boundary is the subagent's actual need. A migration subagent that must edit files needs write tools; a reviewer does not. The nearby opposite case is a coordinator that grants a validation subagent broad tools "just in case"; the fix is that broader scope raises risk without helping the review.
Recurring specifics. the tested material: "Define a security-reviewer subagent with read-only tools in the SDK's agents parameter and include the Agent tool in allowedTools so it can be invoked." The phrase "read-only tools" and "least privilege" recur.
Proposal. give the reviewer the full tool set for flexibility.
Why it attracts. no config friction.
Why it fails. violates least privilege and raises blast radius.
When it would be right. never for a read-only review.
- A mutation offers a broad-tool validation subagent; the fix is to scope it to read tools plus the specific decisions handed into the prompt (Rule 13).
Adaptive decomposition lets plan mode map dependencies and emit subtasks as they surface
For entangled work, plan mode should not commit to one fixed up-front sequence. Instead it maps the dependency graph first, then generates re-sharding or extraction subtasks adaptively as each newly inspected unit reveals which downstream objects it touches. the tested material's 300-table billing migration routes the 60 entangled tables into plan mode "where the plan starts by mapping the foreign-key, materialized-view, and ETL dependencies, then generates re-sharding subtasks adaptively as each newly inspected table reveals which downstream objects it touches".
Adaptive plan for entangled tables:
step 1: map FK + materialized-view + ETL dependencies
step 2: for each inspected table, emit a re-sharding subtask
step 3: repeat as newly inspected tables reveal new downstream objects
(no migration file edited until the dependency map is complete)A fixed sequence assumes the dependency graph is known before inspection, but for entangled tables the graph is discovered during inspection. Adaptive decomposition avoids baking in an order that a later table invalidates. This is the plan-mode counterpart of routing by task shape (Rule 20): the entangled minority is both planned and allowed to re-plan its subtasks as facts emerge.
Boundary. The boundary is whether dependencies are knowable up front. Mechanical tables with a codemod have no hidden dependencies, so a fixed order (or direct execution) is fine. Entangled tables do not, so adaptive decomposition is required. The nearby opposite case is planning the 60 as "one fixed up-front sequence derived from the initial DDL read"; the fix is that the initial read cannot see what later tables reveal.
Recurring specifics. the tested material's adaptive plan: "map the foreign-key, materialized-view, and ETL dependencies, then generate re-sharding subtasks adaptively as each newly inspected table reveals which downstream objects it touches, before any migration file is edited."
Proposal. plan all 60 as one fixed up-front sequence.
Why it attracts. looks thorough.
Why it fails. misses late-emerging dependencies.
When it would be right. only if the graph were fully known initially.
- A mutation offers "process top to bottom in the planned order" for all 300; the fix is that the entangled 60 need adaptive subtasks, not a fixed order.
Forking a session compares divergent approaches; it is the wrong tool for a simple fix
Forking a session branches a new session from a shared baseline so the human can explore two different implementations in parallel and compare them. It is a comparison tool, not a mode selector. the tested material lists forking alongside the Explore subagent as a distinct mechanism: forking "supports comparing divergent approaches rather than making one obvious fix".
Forking is for: comparing two divergent implementations from a shared baseline
Forking is NOT for: a single obvious fix, discovery isolation, or mode selectionA simple, unambiguous fix has no divergent approach to compare, so forking adds a second session and coordination cost for nothing. Forking earns its keep only when the human genuinely wants to evaluate two implementations from the same starting point. Confusing it with plan mode or with the Explore subagent mislabels its purpose.
Boundary. The boundary is whether there are two approaches worth comparing. A one-line null check has one approach, so forking is wrong; a feature with two reasonable architectures has two, so forking is a valid way to compare them. The nearby opposite case is using forking to "keep the main session clean" for a trivial fix; the fix is that direct execution already keeps it clean and forking is pure overhead.
Recurring specifics. the tested material: forking "supports comparing divergent approaches rather than making one obvious fix". It appears as a distractor against both plan mode (for architectural comparison it is plan mode that applies) and the Explore subagent (for discovery isolation it is the subagent that applies).
Proposal. fork before a one-line fix to keep context clean.
Why it attracts. hygiene logic.
Why it fails. overhead for a trivial edit.
When it would be right. never for obvious fixes.
- A mutation offers forking to "preserve the original state before editing"; the fix is that a simple edit needs no preserved branch, and version control already covers that.
For strict formatting transformations, few-shot examples beat long prose
For strict formatting, translation, or data transformation tasks (for example INI to YAML), detailed prose instructions are often insufficient and can even confuse the model. Providing two or three concrete input/output examples showing the exact target indentation and key naming is the most robust technique for consistent output. This is a prompt-engineering rule that sits adjacent to plan mode: it is how a planned transformation rule is actually communicated.
Few-shot for INI -> YAML (illustrative):
input: [db]\n host = localhost\n port = 5432
output: db:\n host: localhost\n port: 5432
(2-3 such pairs anchor key naming and indentation)A transformation's correctness lives in the precise syntax, which prose describes poorly but examples show exactly. Few-shot examples anchor the output format far more tightly than paragraphs of rules, reducing the per-file inconsistency the tested material shows (inconsistent key naming and indentation across files). the tested material marks "increase the length of the prose instructions" as incorrect and "provide 2-3 concrete input/output examples" as correct.
Boundary. The boundary is whether the task is a strict syntax transformation. For open-ended design, examples alone are not enough and plan mode's strategy discussion still matters. The nearby opposite case is a multi-file documentation refresh needing a terminology glossary (Rule 21); there few-shot helps the format but the plan-level glossary addresses consistency of meaning, not just syntax.
Recurring specifics. the tested material: "Providing concrete input/output examples (few-shot prompting) is the most robust and highly recommended technique to guide the model's output syntax." The wrong move is "lengthening prose", which "often confuses the underlying model further".
Proposal. lengthen the prose instructions.
Why it attracts. more detail feels safer.
Why it fails. confuses the model; brevity and clarity win.
When it would be right. never as the primary technique.
- A mutation offers a general consistency instruction in
CLAUDE.md; the fix is that concrete examples, not general instructions, drive syntactic consistency.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| Plan mode | Direct execution | Plan is for ambiguous tasks with multiple valid approaches, multi file architectural changes, or unknown dependencies. Direct is for well understood changes with clear scope and known approach. Discriminator is ambiguity not difficulty. |
| Plan alone | Plan then execute hybrid | Plan alone stops at design. Hybrid applies the plan file by file via direct execution. Migrations across many files use the hybrid, pure analysis can stop at plan. |
| Explore subagent | Main agent exploration | Explore runs discovery in an isolated window and returns a summary, keeping the main window clean. Main agent exploration fills the main window with verbose listings that degrade later turns. |
| /plan interactive | --permission-mode plan in -p | /plan switches interactive Claude Code into read only plan mode. --permission-mode plan is the headless equivalent that restricts a print mode session to read only for CI planning without edits. |
| /diff plus /code-review | Plan mode | /diff plus /code-review is a pre ship review workflow for changes already made. Plan mode is a pre change design workflow. They are complementary, not interchangeable. |
| /code-review ultra | Local /code-review | Ultra runs a multi agent review in the cloud for depth at higher cost and time. Local runs a single pass on the local diff. Choose by required depth versus budget. |
Traps
The tempting answer. Take a monolith to microservices restructuring directly because execution feels faster.
Why it fails. Multi file changes with multiple valid approaches need plan mode to evaluate boundaries and dependencies before code. Starting direct discovers coupling late and forces costly rework, which the exam marks as the cost failure when complexity is already stated.
What is correct. Enter plan mode upfront when the stem already implies ambiguity or architectural choice, use Explore subagents to keep discovery isolated, then execute via the decided strategy.
The tempting answer. Enter plan mode for a one function null guard because thoroughness feels safer.
Why it fails. A well defined single function fix with a known cause and stack trace has no design decision, planning adds overhead without changing the outcome and the exam keys direct execution for this shape.
What is correct. Apply direct execution for fixes where what to change and how are both clear and scope is limited.
The tempting answer. Treat plan alone or direct alone as sufficient for a 30 file library migration.
Why it fails. Plan alone never applies the change, direct alone risks applying the pattern inconsistently per file. The hybrid is the tested combination where plan designs consistency and execution applies it file by file.
What is correct. Run plan to produce the per file migration pattern, then apply it via direct execution with consistent verification per file.
The tempting answer. Start direct and switch to plan only when the first surprise appears to avoid over planning.
Why it fails. When the stem already says complex such as restructure the monolith, ambiguity is present at arrival and deferring incurs rework. The switch cost exceeds the upfront plan cost.
What is correct. Choose plan immediately when the description implies ambiguity, defer only when the stem is genuinely ambiguous about whether complexity exists, not when the stem states it.
The tempting answer. Let the main agent list files and graph dependencies directly because an extra subagent feels like indirection.
Why it fails. Verbose discovery clutters the main window and degrades subsequent implementation turns. The Explore subagent's primary value is isolation, keeping the main conversation focused on the decision the discovery informs.
What is correct. Use the Explore subagent for noisy codebase discovery and return only the summary to the main session.
Iterative Refinement
Steer Claude with examples for interpretation noise, test failures for complex logic, and interview questions for unfamiliar domains, batching only when fixes interact.
What you need to know
First output from Claude Code is rarely the final output, and the exam tests which steering technique to reach for first in each failure shape. The pecking order is stable: concrete input output examples for inconsistent interpretation where prose is being parsed differently each run, test driven iteration for complex transformations with edge cases where failures give unambiguous feedback, and the interview pattern for unfamiliar domains where the developer does not know what questions to ask. Choosing the wrong technique wastes turns, choosing the right one shortens the loop deterministically.
Concrete examples are the fix for interpretation variance. When a prose description of a code transformation produces a different structure each run because phrasing leaves room for judgment, adding more precise prose still relies on interpretation and does not close the room. Two or three concrete before and after pairs showing exact input and exact expected output do. The model generalises the pattern from those pairs more reliably than from any description, so two well chosen examples covering the standard case and one edge case set the pattern more effectively than a longer paragraph.
The switch to examples follows a recognisable pattern the exam can describe without naming it. Observe inconsistency where the same prompt yields different outputs on repeated runs, switch to examples by providing pairs, verify generalisation on a new case not in the example set, and add an edge case example only if the standard case now applies but edge handling still misses. Piling on many examples beyond that adds token cost without proportional reliability, and the exam frames many shot as higher cost for nuanced tasks rather than a better default for interpretation noise.
Test driven iteration is the sister technique for complex transformations where edge cases, null handling, and validation must be consistent. Write the tests first covering happy path, edge cases such as null or empty inputs and boundary conditions, and any performance budget, run the suite, and share the failure output with Claude Code. Expected X got Y is the most unambiguous feedback possible, so the model makes targeted fixes whose next run reduces failing tests deterministically, which prose alone cannot do. The workflow maps to the evaluator optimizer pattern where automated tests are the evaluator and Claude is the generator that refines until the evaluator passes.
The interview pattern and feedback delivery shape complete the set. When working in a domain the developer lacks expertise in, have Claude ask questions before implementing about requirements, edge cases, and constraints, which surfaces considerations such as cache invalidation, TTL policies, consistency choices, and failure modes the developer would otherwise miss. How feedback is batched matters too: batch in one message when fixes interact, for example error shape, logging format, and SDK type must align, so the model sees all constraints at once, and sequence one at a time when fixes are independent so the model does not confuse which feedback maps to which code.
Technique hierarchy and why prose alone fails
Examples demonstrate the pattern via input output pairs, test driven iteration shares failures as feedback, and the interview pattern queries requirements upfront. Prose refinement refines wording but still relies on interpretation, so inconsistent interpretation that persisted across two prose attempts is the signal to stop refining wording and provide pairs instead.
Few shot placement amplifies the fix. Examples embedded at the end of the system prompt as close as possible to the user's message carry the strongest influence because model attention peaks at the beginning and end of context. The same principle applies to in conversation examples, proximity to the task increases the example's effect on the next output.
Test driven iteration as evaluator optimizer
The pattern maps to generator plus evaluator plus optimizer where the developer's test suite is the evaluator. Each shared failure reduces the search space because the model must make the next output pass the named assertion rather than satisfy a vague description. The loop is write tests then share failures then targeted fix until green.
Claude Code composes with headless execution here. In -p mode the CI runner executes the suite, collects the failure output, passes it to a Claude Code turn, and receives the patch, so the same evaluator generator shape runs without interactive presence, with incremental findings fed back until new issues drop to zero.
Interview shape for unfamiliar domains
The prompt explicitly invites questions before implementing, for example ask me about requirements, edge cases, and constraints before implementing a caching layer, or enumerate categories such as inputs, outputs, error cases, performance budgets, and integration points. The model then identifies the domain's hidden categories and the developer answers rather than prescribing the solution upfront.
The exam discriminator is knowing versus not knowing the transformation. If the developer knows the exact before and after shape but the model misinterprets it, use examples. If the developer does not know which considerations matter for correctness in the domain, use the interview pattern. Mixing them, for example examples for unfamiliar caching, misses the hidden tradeoff discovery the interview provides.
Batch versus sequential delivery
Batched feedback arrives in a single message that the model processes together, so interacting constraints such as error code field plus structured logging plus SDK type can be resolved coherently in one patch. Splitting them would risk a fix to one that contradicts another, which the exam presents as the cost of sequencing interacting issues.
Sequential feedback arrives as multiple messages over time and is correct when fixes are independent, such as a naming convention issue and an unrelated indentation issue that do not affect each other. Batching independent issues there confuses mapping of feedback to code region, so the exam presents independent fixes as the case for one at a time delivery.
Authoritative mechanism reference
The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.
Mechanism reference
This section documents each mechanism in full depth: exact flags, fields, and value spaces, the order of operations, who owns each guarantee, and the boundary conditions where the mechanism stops helping and the next one takes over.
Mechanism reference: 3.5.1 Concrete paired examples as the primary repair for interpretation variance
Paired examples are the first-line correction when a natural-language description of a transformation is applied inconsistently. The symptom that selects this mechanism is observable variance. The same prose instruction produces structurally different outputs across repeated runs, or two iterations each fix one edge case while breaking another. Variations may appear as different nesting depth, different timestamp formatting, different field ordering, different handling of null versus empty string, or different treatment of quoted delimiters and multiline fields.
The repair replaces abstract description with demonstration. The developer places two or three paired blocks directly in the prompt where the task is described, each block containing an Input: literal and an Expected output: literal for the same logical item. Surrounding prose collapses to a single sentence that names the transformation, because the pairs now carry the specification. The model copies the demonstrated mapping rather than interpreting adjectives such as clean, professional, standard, comprehensive, or nicely. Multishot guidance gives this the explicit rationale that pattern matching against a concrete pair is more deterministic than linguistic inference.
Coverage is achieved not by volume but by spanning the transformation's degrees of freedom. One example covers the standard case, a second covers the most common edge case, and an optional third covers an ambiguous or compound shape. Typical edge anchors are null preservation, empty string versus missing field, duplicate headers, quoted commas, and timestamp format strings. When the output format has more independent axes such as tone, length, and field ordering, the sufficient count may rise to three to five, but quality dominates quantity. A large set drawn from common sites that already convert cleanly does not teach the diverging shapes and increases prompt length without improving coverage.
Verification is built into the mechanism. After the switch to examples, the developer tests on a new input not shown in the example set and checks that the model generalizes the inferred rule. If the standard case is now stable but a specific boundary still fails, the developer adds one targeted example for that boundary rather than rewriting prose. This keeps the signal concrete and prevents reintroducing interpretation drift.
What the mechanism does not own is also part of its definition. Examples do not invent requirements the developer never thought to include. When the bottleneck is unknown unknowns rather than mapping variance, the interview pattern owns the repair. Examples also do not replace a validation gate. A schema or checklist can be a useful complement after the mapping is stable, but it does not teach the mapping itself because a payload can satisfy a schema while still nesting fields incorrectly or choosing the wrong timestamp format.
The documented ordering places examples first for variance and only then heavier mechanisms for deeper gaps. The reason is signal economy. Examples replace the interpretation layer with pattern matching, tests replace manual inspection with an external oracle, and interview replaces assumption with elicited requirements. Adding prose tries to improve the same interpretation layer that is already the source of variance. Changing sampling settings alters randomness, not systematic mapping. Adding a schema adds a constraint on shape, not on how the source maps to that shape. Only demonstration moves the signal from interpreted instruction to copied pattern, which is why the prompt engineering guidance ranks it above prose refinement for this symptom.
In practice the example block is small, labeled, and stable. A good block names the transformation once, then shows literal code or literal JSON for input and expected output. It avoids adjectives inside the examples. The model sees the before and after shape together and infers the rule. The later mechanism sections show how this minimal example discipline composes with tests and interview without duplicating their guarantees.
Mechanism reference: 3.5.2 Test-driven iteration as the primary repair for complex edge space
Test-driven iteration is the primary repair when the transformation is complex, has many edge cases, or has interacting rules that cannot be held in a short prompt. The symptom that selects tests is not variance but coverage failure: the mapping is largely correct for common cases, yet behavior under null versus empty, quoted delimiters, burst handling, idempotency windows, zero amounts, or malformed input still fails, or failures appear in combinations that manual review does not catch.
The mechanism has three invariants that together make it authoritative.
First, the test suite is authored before or alongside the specification and covers the full behavior space. The reference page explicitly requires happy path, edge cases, and performance where applicable. In practice this means at least one assertion for the standard transformation, one for each key boundary such as null preservation, empty versus missing, quoted delimiter, multiline row, duplicate header handling, timestamp normalization, and one for the error shape such as malformed input returning a structured error rather than an exception. Performance expectations, when load-bearing, are expressed as assertions with measurable bounds, not as prose hopes.
Second, the suite is executed and its output is shared verbatim. The loop pastes the exact FAIL: line, the Expected: line, and the Actual: line without paraphrase. The canonical failure shape in the reference page is Expected: null preserved in output JSON paired with Actual: null replaced with empty string "". That exactness is load-bearing. Expected: null preserved versus Actual: empty string names the field, the desired value, and the observed value, so the model can map the failure to the specific branch that produced the replacement. Paraphrasing into null handling is still wrong reintroduces ambiguity and invites a patch to a different branch than the failing one.
Third, each refinement turn treats a failing assertion as a blocker that must be fixed before the next change proceeds, continuing until all tests pass. The model receives the failing block as the next user message, produces a targeted fix, and the developer re-runs the suite. The loop is the evaluator-optimizer pattern applied to code, with the test suite as the evaluator and the model as the generator.
What tests own versus what they do not own is a frequent source of confusion. Tests own complex behavior spaces where an oracle can say Expected and Actual. When no oracle exists because the requirement has multiple defensible interpretations and the deciding rule is not in the source, such as a legacy code mapping that collapses eight codes into four with an ambiguous table, an after-the-fact loop cannot recover the absent intent. That region calls for surfacing interpretations as confirmed input and output pairs before committing, which is an interview concern, not a test concern. Tests also do not replace examples for simple mechanical mappings. If the transformation is a single rule that is misapplied consistently with few edge cases, two paired examples are lighter and faster than a full suite. The boundary toward tests is crossed when the prose or examples describe the mapping correctly yet behavior under specific boundaries still fails, or when failures interact so that fixing one assertion changes the outcome of another and only a suite can hold all constraints in view.
Feedback granularity inside the test loop is also specified. Related failures that share a root cause, such as two assertions that both trace to the same idempotency store logic, belong in one message that names the shared helper and shows both Expected versus Actual pairs together. Unrelated failures that touch different helpers or different tool results belong in separate messages. Severity ordering does not determine grouping. This grouping discipline prevents the model from producing a patch that fixes one assertion while still failing a coupled one, and it keeps independent fixes from diluting attention when mixed together.
The anti-patterns that this mechanism replaces are well described in the tested material and in lesson guidance. Describing all edge cases in one prompt and asking for implementation and tests together in one pass conflates specification and implementation, so tests match what was built rather than what should be built. Generating an implementation first and then generating tests to match it reverses the test-first guarantee and produces tests that encode the current mistakes. Implementing without tests and then asking for a self-review validates against the same ambiguous description rather than an external oracle.
Mechanism reference: 3.5.3 Interview pattern as the primary repair for unfamiliar domains
The interview pattern inverts the normal prompting flow. Instead of prescribing a solution, the developer instructs the model to ask clarifying questions that surface requirements, edge cases, and constraints the developer may not have anticipated. The prompt explicitly names the interview pattern and lists the dimensions to probe. The model produces targeted questions, the developer answers, and only then does implementation begin.
The canonical trigger phrase in the reference page is Before implementing, ask me questions about the requirements, edge cases, and constraints I should consider. The canonical probe set for a caching layer is invalidation strategy, TTL policy, consistency requirements, and failure modes. The model trained on broad domain knowledge then asks questions such as what invalidation strategy is needed, what consistency guarantee is required, what happens when cache and database diverge, how concurrent invalidation should behave, and what the deployment topology is. The answers become the specification that later tests and examples encode.
The reason the pattern dominates for this symptom is that no amount of concrete examples can demonstrate a requirement the developer never thought to include, and no test suite can assert a behavior the developer never thought to specify. When the developer lacks domain expertise, the bottleneck is unknown unknowns. The model, which has broad domain knowledge, pulls the missing dimensions into the conversation before code commits to a shape that omits them. This prevents the specific failure where the first implementation compiles and passes basic checks, yet later review finds that cache invalidation on writes, partial failure handling, or per-user shared state was never specified and must be retrofitted.
Selection between interview and the other techniques is symptom-driven and mutually exclusive at the decision point.
- If the developer can say the same prose produces different outputs across runs and each run interprets edge cases differently, the fix is examples, not interview.
- If the developer can say prose or examples describe the mapping correctly yet behavior under burst handling, zero weights, negative amounts, or token bucket reset still fails across many combinations, the fix is tests.
- If the developer can say you suspect there are failure modes you have not considered, or I am not an expert in this domain, and repeated rephrasing has not improved coverage, the fix is interview before any code.
The boundary is sharp. Asking the developer to provide examples of a desired log format when the missing piece is regulatory policy or invalidation semantics does not help because examples cannot invent that policy. Asking for a test suite for a vague notification system described as send alerts when important things happen also fails because the suite would encode the same incomplete assumptions about delivery channels and urgency. Surveying files with plan mode addresses cross-file scoping and strategy, not domain knowledge gaps that live outside the codebase.
In the broader hierarchy, interview is a precursor. Learning via interview that per-user shared state, a specific invalidation rule, or a compliance retention policy is required becomes the specification that tests and examples later carry. the tested material and lessons treat this as the correct sequence: interview first when the requirement set is incomplete, then tests and examples to make the mapping precise and checkable.
Mechanism reference: 3.5.4 Technique selection hierarchy and its grounding in documented guidance
The three primary techniques form a hierarchy whose selection key is the observed symptom. The hierarchy is not folklore about preference but a mapping from failure source to repair type that is grounded in prompt engineering documentation.
- Symptom
prose interpreted differently each timeselects concrete input and output examples first. The grounding is the multishot guidance that examples let the model infer the pattern more reliably than following prose alone, and the loop engineering lesson that few-shot examples act as training data the agent sees on every run. - Symptom
many edge cases and complex transformationselects test-driven iteration with verbatim failure feedback. The grounding is the evaluator-optimizer pattern that requires external, deterministic verification and the harness guidance that tool results and test output provide that external signal. - Symptom
working in an unfamiliar domain and suspecting missed considerationsselects the interview pattern before any code. The grounding is the prompt overhead analysis that the prompt determines the ceiling and that surfacing hidden requirements in the prompt is the highest-leverage fix when the requirement set is incomplete.
The hierarchy also has an order within each branch. Examples are the first response to inconsistency before heavier test suites. Tests are the heavier response when examples would need to enumerate dozens of behaviors and manual inspection cannot cover combinations. Interview is the precursor when the developer cannot yet name the behaviors that tests or examples should cover. The opposite case to each is the temptation to apply the lighter or more familiar technique to the wrong symptom, such as adding another paragraph of prose to a caching design that actually needs an interview, or writing a test suite for a date parsing library whose real stall is interpretation drift rather than missing coverage. That mis-selection wastes iterations without changing the underlying failure source.
The reference page reinforces the hierarchy with a clear statement that prose rewording, sampling changes, and additional instruction detail do not cure inconsistent interpretation. That claim is grounded in the same multishot guidance: when the cause is ambiguous prose, different phrasings of the same sentence still map to different latent interpretations, and only a demonstration fixes the interpretation to one concrete mapping and removes the degree of freedom that created the run-to-run difference.
Mechanism reference: 3.5.5 Batch versus sequential feedback and dependency grouping
How feedback is delivered matters as much as which technique is chosen. The rule is determined by dependency structure, not by priority, file count, or severity ordering.
Batched single-message feedback is required when fixes interact through shared state, a shared helper, or a shared output shape. The developer describes all interacting defects together in one detailed user message before the model produces the next revision. The message names each defect, shows how they share a boundary, and asks for a single revised version that reconciles them. The model therefore sees all interacting constraints at once and can reason about the dependency graph once, producing a coherent patch rather than three patches that happen to converge.
Recurring batched clusters in the tested material show why this is load-bearing. One cluster is an error response that must include an error_code field, plus logging that must include error_code in structured format, plus client SDK type definitions that must reflect the new field. Another is a normalize_applicant() helper shared by every quote path where policy_id stripping, prior_claims null handling, and hyphenated surname handling each change an input the others depend on. A third is a transaction lock plus stale cache pair that shares a state variable. A fourth is a single get_ranked_results() function where cache key, sort tie-break, and error swallowing each alter an input the others read. In each case, fixing one issue first changes which values exist when the next issue is evaluated, so sequential patching targets the manifestation left by the prior patch rather than the root cause.
Sequential single-issue feedback is required when defects are independent and isolated. The defects sit in different files, touch different tool results, or alter unrelated field names, and each is sent in its own message and verified before the next is addressed. The loop is fix one, re-run the suite or inspection, confirm, then fix the next. the tested material marks pairs such as a naming convention issue and an indentation issue, or an off-by-one in pagination.ts and a missing null check in a different notification/handler.ts, as sequential. Bundling them would force the model to hold two unrelated mappings in one generation step and increase the chance it silently drops one.
Mixed dependencies are the common production case and have a defined two-phase hybrid. When a set contains a blocking field or schema change plus several independents, the workflow is to fix the blocking contract first and verify it, then batch the independents against the corrected contract. The canonical three-issue case is date parsing mishandling timezone offsets, currency formatting using the wrong locale, and an output JSON schema with an incorrect field name, where the schema field is the load-bearing contract both value fixes must conform to. If all three were batched, the model might shape the two value fixes to the old field name and need a second pass. If all three were sequenced individually, two independent value fixes would be unnecessarily serialized. The two-phase approach respects the dependency graph and is explicitly flagged in the tested material as the correct hybrid.
Feedback grouping for test failures follows the same dependency principle, not a managerial sort key. Three tests may fail where two trace to the same store logic and a third asserts an unrelated JSON shape. The two sharing the store are reported together in one message with the store interaction named, and the JSON failure is reported separately. Severity ordering would place a critical JSON fix first even though the two store failures must be understood together, and file count would split the store pair that shares the fix. The rule is root cause grouping, not count or rank.
A related boundary appears after a stall. After two or three cycles with casual language such as make it better, the next message must become a structured triad: exact current behavior, exact desired behavior, and a concrete failing case. That triad names the field, shows the value difference, and provides a reproducible case so the model can fix the correct branch. Vague feedback gives no localized target on the first iteration a short instruction may suffice, but after a stall the triad is the lever. Switching to a new tool or starting over without a triad leaves the stall uncured because the feedback still lacks a target.
The broader lesson from prompt engineering that grounds both batch and sequential rules is that feedback quality is measured by how narrowly it localizes the target. A batched message that names shared state and shows three failure pairs is high signal for an interacting cluster. A sequential chain that fixes one isolated defect at a time is high signal for independent work. Either mode wastes signal when applied to the wrong coupling, which is why the dependency check is the gate.
Mechanism reference: 3.5.6 Structured stall recovery, interruption, and prompt rewriting
Beyond steady-state feedback, the tested material documents three specific stall repairs that go with the hierarchy rather than replacing it.
Structured feedback after a stall is the first. Its mechanism is the triad described above. The concrete example in the tested material shows a transform(record) call where record.nickname is null returning nickname: "" versus the desired nickname: null, with an Input, Expected, and Actual block. For a React case the triad is missing error state, add an error prop that renders a red ErrorBanner when true. The triad is required because vague language has already been tried for several cycles and left the output unchanged. Trying again without context or switching tooling never provides a target, and the stall persists.
Interruption is the correct mid-task correction when the agent broadens scope beyond the intended boundaries. If the agent begins applying validation to all API endpoints when only one category was intended, the developer presses Esc to interrupt and provides a narrower instruction naming the specific category, such as only endpoints under src/api/public/ that use createOrder while leaving src/api/internal/ unchanged. Letting a broad edit finish and then reverting wastes the broad pass and can introduce hard-to-revert changes. Undoing everything and starting over loses established context. Interruption keeps the context and lets the model narrow its search pattern immediately. The mechanism is for scope drift during execution. If the whole task was mis-scoped, a full revert and rewrite of the original prompt may be more efficient, but for live drift, Esc plus a narrower category is the prescribed correction.
Rewriting the original prompt is preferred over patching after repeated patch failures that leave regressions. When two or three patches each fix one defect while reintroducing another, the developer stops appending corrections and rewrites the original request as one consolidated prompt stating the full interacting constraints. For a CSV migration the rewritten prompt becomes a single line such as Convert CSV to JSON where empty cell maps to empty string, quoted comma is kept intact, and duplicate header appends a suffix. For null versus missing it becomes a prompt with two explicit examples that distinguish missing tag mapping to null and empty tag mapping to empty string. The reason is that a thread dominated by failed attempts fills the context window with contradictory reasoning, and the model anchors on which fix broke which other. Rewriting presents the combined truth once and prevents each iteration fixing one scenario while breaking the other.
That rewriting cure is especially effective when prose refinement has plateaued and fresh shapes keep surfacing. Evidence shows cases where natural-language guidance and exhaustive clause lists reduced divergence rate without eliminating it, with each new call-site shape triggering another round of divergence. The remaining cure is few-shot pairs that include both the before and after and the reasoning that selects each mapping, plus one deliberately ambiguous case where the reasoning must choose between competing semantics. A fixed set of verbatim pairs for each observed shape works only when no new shapes will appear. Reasoning-rich examples let the model generalize the selection rule to novel patterns rather than matching each new file to its closest verbatim example.
All three stall repairs respect the same ownership principle that feedback must be concrete and localized. Structured triads localize to a field, interruption localizes to a path category, and a rewritten prompt localizes to a complete interacting constraint set. Each trades a vague instruction for a specific target, which is why they are treated as refinements inside the iterative loop rather than as escapes from it.
Mechanism reference: 3.5.7 Session continuity, resumption, and branching from a clean baseline
Claude Code sessions are transcript-anchored. The conversation, including tool calls and their results, is persisted to a transcript on disk. That transcript is what makes continuation and branching possible, and its storage and replay semantics determine what a resumed session does and does not refresh.
Linear continuation is provided by claude --continue and claude --resume <session-id-or-name>. claude --continue resumes the most recent session in the current directory. claude --resume <session-id-or-name> resumes a specific named session. In both forms the flags restore the conversation history and context from the saved transcript. This is confirmed in the dedicated forking lesson, which states that resumption restores conversation history and context, does not restore in-memory runtime state such as variables or process state, and does not know which file writes were already committed to disk before the session ended.
Working directory is load-bearing for resumption. Transcripts are stored under a path derived from the working directory where the session started. Resuming from a different directory fails to locate the transcript even with the correct session identifier and the agent starts fresh. The lesson explicitly flags this as the reason to always resume from the same directory the original session ran in.
What resumption does is restore the full message history so follow-up questions see prior analysis. What it does not do is refresh the world. File contents captured as tool_result values inside the transcript are snapshots from the time of the original Read or Grep. They are inline history, not live views. Verbal notice that files 3, 7, and 11 were rebuilt, without a fresh Read, leaves the old text deterministically in context, so timeline entries and citations keep referencing code paths no longer present. The lesson states this explicitly as the Resume, then re-verify the world pattern: after resuming, the agent must be told which files changed so it re-analyzes only those areas, and the transcript must not be assumed to match the current filesystem or git state.
The forensics file flagged two uncertainties for this area. The first was the correct spelling of the session-branching primitive, cited in evidence as both fork_session and a spaced variant. The authoritative lesson resolves this. The interactive commands are /fork and /compact, and the environment control for forking is CLAUDE_CODE_FORK_SUBAGENT. the tested material spelling fork_session is a transcription of the internal tool identifier rather than the interactive command spelling. Writers should use /fork for the slash command and describe the primitive as the fork subagent or fork_session primitive only when referring to the underlying mechanism, making the mapping explicit rather than leaving both spellings floating.
The second flag was whether resuming and then re-reading the rebuilt files refreshes the view sufficiently when stale results are present, or whether a fresh session before branching is safer. The lesson and the tested material converge on a nuanced answer. Re-reading inside a resumed session does bring fresh Read results into the new tail of the conversation, so the model can see current contents for those specific files. However, the resumed session still carries the old conversation's framing, the stale tool results for the other files, and the prior reasoning that was anchored to the old truth. Telling the model that seven of twenty-two files were rebuilt via a verbal summary without re-reading does not evict the stale snapshots. Re-reading only the seven rebuilt files while leaving the other fifteen cached results intact is marked as insufficient in the tested material because it leaves the session's baseline contaminated. The documented safe practice when core evidence has changed, such as source documents replaced and a web index refreshed, is to start a new session seeded with a structured summary of durable findings plus an explicit list of changed sources for fresh analysis, then fork from that clean baseline if parallel exploration is needed.
Forking semantics reinforce this hygiene requirement. A fork inherits the entire parent conversation so far, including system prompt, tools, and message history at the moment it was created. Its own tool calls stay out of the main conversation and only its final result returns, so the main window stays clean while the fork works. The prompt cache is shared with the main session on the fork's first request, which makes a fork cheaper than a fresh subagent for tasks that need the same context. A fork cannot spawn further forks, so the topology is a flat fan-out rather than a recursive tree. Critically, forking preserves whatever baseline it was given. If that baseline is contaminated with stale tool_result contents, both forks inherit the contamination. That is why the tested material marks resuming a stale session and then forking from it as a failure mode. The baseline must be clean before branching.
Crash recovery adds one more boundary. After a crash mid-task, --resume restores the conversation but cannot tell which edits already landed on disk. For large multi-file work the lesson directs teams to design idempotent writes or to track completed steps externally so re-applying changes is safe, rather than assuming the transcript knows which file writes were durably committed.
Checkpointing and compaction are often confused with resume and are worth separating here because they affect what survives a long session. Checkpointing via /checkpoint and /rewind snapshots the conversation and the file state edited through the agent's own tools, with periodic automatic snapshots between manual ones. /rewind rolls both the conversation and those tracked files back to an earlier checkpoint. It does not undo external side effects such as API calls, database writes, deployments, or changes made by manual Bash commands or external processes. Compaction via /compact summarizes and compresses the conversation to free context space. It does not change files on disk at all, and the summary is lossy, so exact details from earlier turns can disappear. The lesson contrasts them directly: checkpointing can restore files, compaction never touches the filesystem.
Non-interactive and SDK surfaces mirror the interactive semantics with additional detail. Inside a headed or headless flow the same transcript path controls whether continuation finds prior state. The reference page evidence that cited claude --resume breach-timeline to continue a named investigation with twenty-two catalogued files and seven rebuilt ones is consistent with the lesson when the resumed session is treated as a conversation restore that then needs explicit re-reads or, better, a fresh summary-seeded session when the staleness is load-bearing. Bulk headless work with claude -p and --allowedTools is the alternative to resuming a single interactive window for mechanical per-file tasks and is covered in the next section.
Mechanism reference: 3.5.8 Context hygiene, bulk scripting, and loop control
Three mechanisms that the reference page touches through its build exercises and that the forensics file treats as part of iterative refinement deserve explicit reference treatment here because they determine whether refinement converges or degrades.
Context hygiene is the discipline of keeping the active context proportional to the task as a session grows. Claude Code's window is finite, and in long sessions old context accumulates as stale file contents, superseded decisions, and resolved errors. Symptoms include repeating resolved errors, applying old conventions, losing track of decisions, and asking what was already discussed. The lesson prescribes four concrete counters: use /clear between unrelated tasks, scope file loading to the task by pointing at the specific files relevant now, keep CLAUDE.md clean by removing outdated rules, and keep ephemeral notes in CLAUDE.local.md rather than polluting shared state. When a session passes the thirty to forty-five minute window for complex work, the signal is to end the session, summarize what was accomplished, and start fresh. For deep explorations where verbose discovery fills the window, the correction has two parts: persist confirmed findings to a small findings.md scratchpad on disk and consult it before answering, and run /compact to compress heavy discovery output while keeping the externalized findings authoritative. Maintaining a growing discovered components block inside the live conversation is not a fix because the block itself competes with heavy dumps and still sits inside the degrading medium.
Bulk mechanical work has its own hygiene rule that replaces long interactive refinement. For a migration that touches many modules, such as the 1,800 module case in the tested material, the disciplined path is to generate a file list and loop calling claude -p once per module with --allowedTools pre-approving the needed tools. Each invocation starts with a fresh window, so no run accumulates hundreds of file reads and edits. The companion discipline is to make the work idempotent by writing a script that checks which files still lack the desired state and passing only the unprocessed files to the next run. A prompt flaw is caught by refining the prompt on the first two or three files before launching the full batch. The lesson marks a single long interactive session that tries to migrate file by file as the failure mode for this class, and marks re-run the same command and let the model skip already modified files or increase max_turns as insufficient because the model does not automatically detect completed files and longer runs compound pollution.
Loop control determines how an iterative system knows when to continue. the tested material and workflow lessons distinguish three signals: deterministic runtime state such as stop_reason or explicit verification output, a fixed iteration ceiling, and assistant text such as pipeline complete or alignment finished cleanly. Only the deterministic signal should drive continuation. While the field is tool_use the harness appends each tool_result back into the conversation and continues. When it is end_turn the loop terminates. A high iteration ceiling remains only as a runaway safety net, not as the primary stop condition, and tool results are appended so the next decision reflects the latest verification state. Terminating on a recognized completion phrase is fragile because that language can appear mid-pipeline for both high and low quality samples and varies with wording, and a fixed cap such as five iterations cuts off legitimate long analyses. Replacing an adaptive loop with a fixed four-stage pipeline removes adaptivity to read depth and contamination flags that vary per sample.
Codebase discovery has a direct analog to iterative hygiene. In an unknown monolith with more than a thousand files and no architecture docs, the disciplined discovery pattern is to use Grep to find an entry point such as discount symbols or price calculations, read only the matching functions with offset Read, then follow their imports and call sites with further targeted Grep and offset reads. Bulk Glob plus full Read of whole directories saturates the window with unrelated code before relevant modules are identified, causing the agent to propose edits in the wrong module. Even adding read efficiently to the system prompt is flagged as insufficient if it still front-loads large sets before any concrete lead.
Evaluation hygiene completes the picture. A prompt iterated against a clean distribution that lacks messy production variants, such as typos, long rambling multi-part inputs, and irrelevant personal details, can pass its suite while production traffic with those variants still fails. The fix is to sample recent production inputs and add cases that mirror those variants, then re-measure. Expanding with more clean variants or raising an accuracy threshold does not move the metric that never observed the failing behavior. The same principle applies to the build exercises: the suite must include the messy rows such as quoted commas and empty cells if the claim is that it guards the transformation.
Ownership map
Each layer owns a distinct guarantee. The model owns pattern matching, generalization from examples, and broad domain knowledge that the interview pattern elicits. The prompt and the harness own technique selection and feedback grouping. The developer owns the oracle: the test suite that says Expected and Actual, the evaluation distribution that mirrors production, and the durable summary that seeds a fresh baseline when stale snapshots would otherwise contaminate a branch. The CLI and config layer owns enforcement that prose cannot provide: the allow, ask, and deny tiers in .claude/settings.json, the CLAUDE_CODE_FORK_SUBAGENT flag that gates /fork availability, and the transcript path derived from the working directory that determines whether --resume finds prior state. Infrastructure owns the persistence of transcripts, the shared prompt cache that makes a fork cheaper than a fresh subagent on its first turn, and the isolation of a fork's tool calls from the main window. Conflating these owners produces the recurring anti-patterns: putting destructive command guards in CLAUDE.md text instead of deny rules, keeping an idempotency check in prose instead of a suite, or announcing file rebuilds verbally instead of seeding a fresh summary plus explicit Read calls.
Version and terminology currency
Several terms in this area have drifted between the exam guide generation and current product usage. This section records the durable form that a candidate should use and notes the older alias where confusion is likely.
- Slash command spelling. The interactive commands are
/fork,/clear,/compact,/checkpoint, and/rewind. the tested material transcriptionfork_sessionand the lesson title that includes it refer to the internal subagent primitive that backs/fork, not to a distinct top-level CLI flag. There is no standalone--forkflag that launches a separate top-level session. The version gate for the primitive is Claude Code v2.1.117 or later, with/forkon by default from v2.1.161 and controlled in any version byCLAUDE_CODE_FORK_SUBAGENT=1to enable and0to force-disable, including any server-side staged rollout.
- Session continuation flags.
claude --continueis the shorthand for the most recent session in the current directory.claude --resume <session-id-or-name>is the explicit named form. Both restore the saved transcript, not runtime state. The forensics evidence that citesclaude --resume breach-timelineis consistent with the current naming, where a human-chosen name serves as the session identifier.
- Headless execution.
claude -p "<prompt>"is the non-interactive entry point. The long form is--print. The flag that appears in CI contexts is-por--print, not a generic preference flag. The correct per-invocation override flags are--allowedTools,--permission-mode,--max-turns, and--append-system-prompt; there is no--prefstyle generic override and it should be treated as a distractor.
- Memory layering. Project
CLAUDE.md, walk-up directoryCLAUDE.mdfiles, andCLAUDE.local.mdare concatenated into context, not merged by one overriding another. Content closer to the working directory is read last and has the strongest last word without discarding anything. This matters when iterative refinement guidance is placed in memory: the most specific scope should hold the current iteration's constraints so they are not diluted by broader defaults.
- Prompt engineering stable guidance. The current prompt engineering overview emphasizes being clear and direct while acknowledging verbosity tradeoffs, and the dedicated multishot page and long-context tips give the stable guidance that examples carry more signal than description. Those pages are the right anchors for the ordering claim rather than any ephemeral blog phrasing.
Official versus community divergence
Where community material diverges from Anthropic documentation, the documentation position controls what a candidate should answer.
- More prose to fix variance. Community threads often suggest that rewriting a prose description with more precise language and technical terminology will cure inconsistent interpretation. Anthropic multishot documentation states the opposite for this symptom: showing input and output pairs lets the model infer the pattern more reliably than following prose, and additional adjectives do not remove the interpretation layer that causes run-to-run variance. A candidate should answer with examples first for that symptom.
- Schema or checklist as a substitute for examples. Community recipes sometimes present a JSON schema or a numbered checklist as the rigorous fix for mapping tasks. Documentation and lesson guidance treat those as complements, not substitutes. A schema validates shape, not content mapping, and a checklist still requires judgment about what each category means for a specific function. The tested position is that concrete input to output pairs dominate both for teaching a mapping.
- Temperature or sampling changes to cure systematic misinterpretation. Community advice occasionally frames lower or higher sampling settings as a determinism fix. Documentation defines sampling controls as governing randomness, not mapping correctness. The tested position is that systematic misinterpretation is cured by demonstration or by an oracle, not by sampling.
- Fork and resume hygiene. Community summaries sometimes describe resuming a named session and then verbally listing which files were rebuilt as sufficient to refresh the model. The dedicated Claude Code lesson states that tool results in the transcript are snapshots and that resumption restores the transcript, not live file state. The safe documented pattern when core evidence has changed is to start a new session seeded with a structured summary and an explicit list of changed sources for fresh
Readcalls, rather than relying on verbal notice inside a resumed session. Forking from a stale baseline copies the stale baseline into each branch, which is why the baseline must be clean before branching.
- Loop control wording. Community scripts often terminate on a recognized completion phrase such as pipeline complete. Documentation and workflow lessons treat only deterministic signals such as
stop_reasonand explicit verification output as authoritative for continuation, with a safety ceiling as a secondary guard. Phrase matching is marked as fragile for both high and low quality samples.
Beyond the task statement
The adjacent material that the wider lesson set covers, but that the reference page omits or touches only lightly, is load-bearing for production work that uses iterative refinement. Each item below names what it is, why it matters for this task, and the exact lesson slug where it is taught.
- Prompt is the program inside a loop. The loop engineering lesson frames the prompt as the code that runs on every iteration, so a flawed prompt produces flawed results at scale. This matters because the technique hierarchy is not just a style choice but a ceiling-setting decision for the loop. Slug
loop-eng-prompt-engineering.
- XML structured prompting as the harness contract. The lesson teaches XML tags as the contract between agent and harness for reliable parsing and extraction across iterations, including tool result wrapping and structured output. This matters because a batched feedback message that mixes three interacting defects is only useful if the harness can extract each field and the model can reference sections by name without format drift. Slug
loop-eng-prompt-engineering.
- Chain of thought as the planning step before action. The lesson treats few-shot plus chain of thought as the agent's planning step, with a repeatable template of goal assessment, state inventory, plan formulation, execution, and verification. This matters because iterative refinement in complex migrations benefits from attaching reasoning to examples so the model generalizes to unseen shapes rather than matching verbatim pairs. Slug
loop-eng-prompt-engineering.
- Subagent versus fork selection. The best practices lesson and the forking lesson together teach that a regular named subagent starts fresh and needs the situation re-explained, while a fork inherits the full parent conversation and shares the prompt cache on its first turn. This matters because the cost of re-briefing versus the benefit of cache sharing changes whether parallel refinement should use forks or fresh subagents, and because a fork cannot spawn further forks, so the topology must be a flat fan-out. Slugs
best-practicesandclaude-code-fork-session.
- Self-improvement loop via
CLAUDE.md. The best practices lesson defines the four-step loop of complete task, identify gaps, updateCLAUDE.mdwith a concrete rule, and verify in the next session. This matters because corrections such as useasync/awaitnotthenchains or run tests after every code change must compound across sessions rather than being re-explained in every refinement prompt. Slugbest-practices.
- Permission tiers as deterministic enforcement. The best practices lesson teaches allow, ask, and deny in
.claude/settings.jsonas the layer that enforces policy, withCLAUDE.mdprose treated as advisory. This matters because iterative work that includes destructive commands must not rely on prose discouragement but on deny rules. Slugsbest-practicesandconfiguration.
- Evaluator-optimizer pattern and its convergence criteria. The workflow patterns lesson defines the generator to evaluator to refine loop with explicit ready checks. This matters because test-driven iteration is an instance of that pattern and needs a defined stopping condition such as all tests passing or a score threshold, without which the loop can be mistaken for an indefinite retry. Slug
workflow-patterns.
- Parallelization versus chaining versus orchestrator selection. The workflow patterns lesson contrasts sequential chaining, routing, parallelization, orchestrator subagents, and evaluator-optimizer by latency and by whether step two depends on step one. This matters because iterative refinement at scale, such as bulk migration with
claude -p, is a parallelization choice, while a single-file refinement is a sequential evaluator loop, and choosing the wrong pattern adds latency or loses the ability to keep attention focused. Slugworkflow-patterns.
- Session isolation for review. The workflow patterns lesson states that review should run in a separate session from generation to avoid same-session intent bias, where the model reviews what it intended to write. This matters because iterative refinement that leaves review inside the generator session will confirm its own work and miss logic errors even when the suite is still failing in subtle ways. Slug
workflow-patterns.
- Prompt anti-patterns that compound in loops. The loop engineering lesson enumerates over-constraining, under-specifying, contradictory instructions, and feedback blindness as anti-patterns that compound when repeated across iterations. This matters because a batched feedback message that is over-constrained or contradictory will cause oscillation rather than convergence. Slug
loop-eng-prompt-engineering.
- Extended thinking budget exhaustion. The loop engineering lesson warns that reasoning tokens accumulate per iteration and that a retry should get a smaller reasoning budget than the first attempt. This matters for large refinement loops where internal reasoning dominates cost and latency if not scaled down on retry. Slug
loop-eng-prompt-engineering.
Worked production examples
This section contains end-to-end walkthroughs that prove the mechanisms above with concrete values, observable outputs, and the failure mode each avoids. Five substantial language-tagged examples are included as required, plus an integrative walkthrough that shows how the techniques compose.
Worked production examples: Example A: Paired input and output examples for a code transformation [required: paired examples]
Context. A TypeScript service has twenty handlers that wrap API errors with a legacy ApiError shape. The team wants to migrate call sites from unwrapped Promise<UserData> returns to Promise<Result<UserData, ApiError>> wrapped returns. Prose instruction alone had been tried as Wrap the fetch functions so they return a Result with ApiError on failure. The model produced three different shapes across three runs: one that wrapped the whole promise, one that wrapped only the error branch, and one that introduced an extra data nesting level. The symptom is interpretation variance, so the correct hierarchy call is examples first.
Prompt with paired examples. Two to three literal pairs are placed where the task is described. The surrounding prose is reduced to a single naming sentence because the pairs now carry the mapping.
// Transformation: wrap async fetch return type with Result<UserData, ApiError>
// Input:
export async function getUserData(userId: string): Promise<UserData> {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new ApiError(res.statusText);
return res.json() as UserData;
}
// Expected output:
export async function getUserData(userId: string): Promise<Result<UserData, ApiError>> {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) return err(new ApiError(res.statusText));
const data = (await res.json()) as UserData;
return ok(data);
}// Input:
export async function fetchOrders(customerId: string): Promise<Order[]> {
const res = await fetch(`/api/orders?customer=${customerId}`);
if (!res.ok) throw new ApiError(res.statusText);
return res.json() as Order[];
}
// Expected output:
export async function fetchOrders(customerId: string): Promise<Result<Order[], ApiError>> {
const res = await fetch(`/api/orders?customer=${customerId}`);
if (!res.ok) return err(new ApiError(res.statusText));
const data = (await res.json()) as Order[];
return ok(data);
}// Third example, edge shape: handler that returns null for missing user
// Input:
export async function getUserOrNull(id: string): Promise<UserData | null> {
const res = await fetch(`/api/users/${id}`);
if (res.status === 404) return null;
if (!res.ok) throw new ApiError(res.statusText);
return res.json() as UserData;
}
// Expected output:
export async function getUserOrNull(id: string): Promise<Result<UserData | null, ApiError>> {
const res = await fetch(`/api/users/${id}`);
if (res.status === 404) return ok(null);
if (!res.ok) return err(new ApiError(res.statusText));
const data = (await res.json()) as UserData;
return ok(data);
}What this proves. After the switch, three new runs on unseen handlers such as fetchInvoices and getProfile produce the same wrap pattern, with 404 preserved as ok(null) rather than mapped to err. The observable output is consistency across runs and correct generalization to the null case that was shown only once as an edge anchor. The failure boundary that is avoided is run-to-run nesting drift. Adding a fourth or fifth example drawn from common sites that already convert cleanly would increase length without improving coverage of the diverging shapes, and a prose-only retry would leave the same variance intact.
Worked production examples: Example B: Failing test suite driving iteration [required: failing test suite]
Context. A CSV to JSON converter must handle standard rows, quoted commas, multiline fields, empty cells, and duplicate headers. The mapping prose looked complete, yet behavior under edge rows kept failing and fixing one edge broke another. The symptom is a complex behavior space with many combinations, so the correct hierarchy call is test-driven iteration with verbatim failure sharing.
Test suite written first. The suite is authored before implementation and covers happy path, key edge cases, and a malformed case. Each expectation states the exact field and value.
// tests/csv-to-json.test.ts
import { describe, it, expect } from "vitest";
import { transform } from "../src/transform";
describe("csv to json converter", () => {
it("preserves null as null and empty as empty string", () => {
const input = { name: "Ada", nickname: null, tag: "" };
const result = transform(input);
expect(result.nickname).toBeNull();
expect(result.tag).toBe("");
expect(result.name).toBe("Ada");
});
it("keeps quoted comma intact inside a field", () => {
const row = `"a, b",c`;
const result = transform({ row });
expect(result.fields).toEqual(["a, b", "c"]);
});
it("handles duplicate header by suffixing second occurrence", () => {
const result = transform({ headers: ["name", "name"], rows: [["a", "b"]] });
expect(result.headers).toEqual(["name", "name_2"]);
});
it("returns empty string for empty cell not null", () => {
const result = transform({ headers: ["a", "b"], rows: [[",", ""]] });
expect(result.rows[0][0]).toBe(",");
expect(result.rows[0][1]).toBe("");
});
});Failing run shared verbatim. The suite is executed and the output block is pasted without paraphrase as the next user message.
FAIL tests/csv-to-json.test.ts
- preserves null as null and empty as empty string
Expected: null preserved in output JSON
Actual: null replaced with empty string ""
- keeps quoted comma intact inside a field
Expected: row with quoted comma yields two fields ["a, b", "c"]
Actual: row split into three fields ["a", " b", "c"]
Tests: 2 failed, 2 passed, 4 totalRefinement turn. The model maps the first failure to the branch that coerces null to "" and the second to the splitter that ignores quote state. A single patch addresses both because the current two failures are related through helper boundaries only insofar as they share the same transform entry point, but the fixes are still localized to distinct branches. The developer re-runs npm test and treats any remaining failure as a blocker before requesting further changes. The observable outcome after one iteration is a reduction from two failures to zero on these assertions, with no room left for interpretation about which field or which delimiter was intended. The failure boundary avoided is the proxy feedback loop where manual prose corrections fix one row shape while breaking another, because the suite holds all shapes in view at once.
Worked production examples: Example C: Interview-style prompt for an unfamiliar domain [required: interview prompt]
Context. A product team needs a caching layer for an API but has no prior experience with cache invalidation semantics at scale. The request as first drafted was Build me a caching layer for the API. If executed as written, the model would commit to a shape with default invalidation, a guessed TTL, and an assumed deployment topology that later review would reject. The symptom is unknown unknowns, so the correct hierarchy call is interview before code.
Interview prompt sent before implementation.
I need a caching layer for the API. Before implementing, ask me questions
about the requirements, edge cases, and constraints I should consider.
Focus on invalidation strategy, TTL policy, consistency requirements,
and failure modes. I am not an expert in this domain and want to surface
hidden considerations before we commit to an architecture.Model response that the pattern is designed to elicit.
Questions before design:
1. What invalidation strategy do you need: write-through, write-behind, or cache-aside, and what triggers invalidation on mutation?
2. What consistency guarantee is required between cache and database, and what may diverge temporarily?
3. What happens when cache and database diverge and a reader observes stale data?
4. How should concurrent invalidation requests behave, and is per-user Redis shared state shared across writers?
5. What is the deployment topology, single region or multi-region, and how does that affect replication and eviction?
6. What failure mode is acceptable when the cache is unavailable: fail open with a slow path or fail closed with a structured error?
7. What TTL policy do you want, fixed window or sliding, and what eviction signal governs memory pressure?What this proves. The seven questions pull invalidation, TTL, consistency, failure, and topology dimensions that prose examples for a known log format could never invent. The answers become the specification that the next two mechanisms encode: tests that assert the chosen consistency and failure behaviors, and examples that show the desired response and log shapes. The failure boundary avoided is omitting the load-bearing dimension entirely and then retrofitting it after the first implementation has already been reviewed and found incomplete.
An alternative prompt that would be wrong for this symptom is Provide 2 to 3 concrete input and output examples for the desired response shape. That prompt is correct when the mapping is known and only its demonstration is missing, but it cannot surface a policy that the developer never thought to include. Similarly, writing a test suite first for a notification system described only as send alerts when important things happen would encode the same incomplete assumptions about delivery channels and urgency the suite should have caught, which is why interview precedes the suite when the domain is unfamiliar.
Worked production examples: Example D: Batched feedback message for interacting fixes [required: batched feedback]
Context. A shared normalize_applicant() helper sits at the center of every quote path. Three defects are open against it: policy_id fragments need stripping, prior_claims null needs a specific handling, and hyphenated surnames need correct normalization. Each fix changes an input the others depend on. Evidence marks this as the canonical interacting cluster where batching is required, and the mixed dependency guidance says a shared helper is the tell.
Batched single message sent before the next model revision.
Three changes needed. They interact with each other because every quote path
calls `normalize_applicant()` before validation and the helper writes the same
record shape that both `policy_id` and `prior_claims` flows depend on.
1. In `src/applicant/normalize.ts`, the `normalize_applicant()` helper must
strip `policy_id` fragments. Input `policy_id: "POL-001/02"` must become
`policy_id: "POL-001"` and the trailing fragment `"/02"` must be dropped
before downstream validation.
2. In the same helper, `prior_claims` handling must preserve `null` as `null`
and map empty string to `null` only when the field is missing, not when the
caller explicitly passed `""`. Input `{ prior_claims: null }` must remain
`{ prior_claims: null }` and input `{ prior_claims: "" }` must remain
`{ prior_claims: "" }` unless the key is absent.
3. Hyphenated surnames must keep the hyphen intact. Input `last_name: "Smith-Jones"`
must stay `"Smith-Jones"` after normalization and not be split or lowercased
beyond the existing case rule.
Please address all three together and ensure the helper, its callers in
`src/quote/*.ts`, and the validation step agree on the final record shape.What this proves. The model sees the shared helper, the shared record shape, and the validation dependency in one message, so it can reason about mutual effects once and produce a single coherent patch rather than three patches that fix one defect in a way that conflicts with the next. The observable output is a single revised normalize_applicant() plus call-site consistency, verified by a small suite that asserts the three transformations on the same input record. The failure boundary avoided is sequential patching where the fix for policy_id changes which keys exist when prior_claims is evaluated, so the model corrects the manifestation left by the prior patch rather than the root cause.
The nearby opposite case is also shown in the same evidence set. If the open issues were a naming convention update in src/utils/format.ts and an indentation fix in the same module that do not touch the same helper or contract, the same batching would be wrong. The correct delivery there is sequential, with wait for result between messages, because the issues are isolated and bundling dilutes attention and risks silently dropping one.
Worked production examples: Example E: Session continuation and branching from a clean baseline [required: session and branching]
Context. A forensics investigation catalogued indicators in twenty-two files and later learned that seven of those sources were rebuilt between sessions, with a web index refresh. The team needs to explore two divergent containment narratives, one centered on credential rotation and one on session invalidation, without cross-contamination and without carrying stale snapshots.
Step one. The safe resumption path when core evidence has changed is not to rely on a verbal notice inside a resumed window. It is to start a fresh session seeded with a structured summary of durable findings plus an explicit list of changed sources for fresh analysis. The durable summary names exact file locations and the severity of each indicator, while the changed list tells the model which files must be re-read.
# Start a fresh session seeded with a durable summary and a changed source list
# Do not resume the stale session with a verbal rebuild notice alone
claude "You are reviewing a forensics timeline. Durable findings are in
findings.md with exact file locations and severity. Seven sources were rebuilt
since the last run: src/auth/session.ts, src/auth/rotate.ts,
src/api/public/createOrder.ts, and four others listed in changed.txt.
Re-read only those seven sources fresh and compare them against findings.md
before proposing a new timeline. Do not cite the old tool results for those
paths."
# Inside that fresh baseline, verify changed sources explicitly
# Each Read is an explicit tool call whose result replaces the stale snapshotStep two. From that clean baseline, branch with a fork to keep the two narratives isolated. The baseline already contains the shared OpenAPI spec, the migration map, and the re-verified findings, so neither fork needs to re-explain the situation. Each fork's own tool calls stay out of the main window and only its final result returns.
# From inside the fresh baseline session, fan out with forks
/fork Evaluate containment narrative A: credential rotation. Use the shared
OpenAPI spec and findings.md as the baseline. Produce a timeline and a cost
estimate for rotation, then stop. Do not touch the main window's state.
/fork Evaluate containment narrative B: session invalidation. Use the same
baseline. Produce a timeline and a blast radius estimate for invalidation,
then stop. Compare both branches after both complete.
# Fork availability is gated by CLAUDE_CODE_FORK_SUBAGENT
# CLAUDE_CODE_FORK_SUBAGENT=1 enables /fork in interactive, -p, and SDK surfaces
# CLAUDE_CODE_FORK_SUBAGENT=0 force-disables it everywhereStep three. The continuation hygiene for a less disruptive change, where only a small delta occurred and the transcript is still largely valid, is the lighter resume-then-re-verify pattern. claude --continue or claude --resume breach-timeline restores the prior transcript and context, after which the developer tells the agent which files changed so it re-analyzes only those areas. The observable difference between the two paths is what the model cites. In the fresh-seeded path, citations reference the newly read rebuilds. In a stale resumed path that only received a verbal notice, citations keep referencing Day 1 contents. The forking lesson also flags that re-reading seven rebuilt files inside a resumed session does bring fresh results into the new tail, but the old framing and the other fifteen stale snapshots still contaminate the baseline, and a fork from that contaminated baseline copies the contamination into both branches.
The failure boundary avoided is quoting code that no longer exists while appearing to have refreshed the world, and the governance boundary is that forking respects the prompt cache economy: the fork's first request shares the parent's cache, making it cheaper than a fresh named subagent when the task needs the same context, but the fork's own turns still cost normally as it diverges, and a fork cannot spawn further forks, so a flat fan-out must be planned rather than a recursive tree.
Worked production examples: Example F: Integrative loop that sequences interview, tests, and examples with correct feedback grouping
This integrative example shows the hierarchy in one flow. A team inherits a payment module with zero amounts, currency precision, and idempotency collisions as the hidden edge cases. The first step is interview because no existing doc lists those boundaries. The interview prompt asks the model to surface requirements and failure modes before design. The answers name the three boundaries explicitly. The team then writes a small suite that asserts the three behaviors, runs it, and shares the two related idempotency failures together in one batched message because they share a store, while the malformed JSON case is reported separately. Once the suite passes, a pair of paired examples shows the canonical Amount handling mapping so that future call sites generalize the rule without needing a full suite for each file. The suite is the oracle, the examples are the teaching signal, and the batch versus sequential choice follows the shared store as the dependency tell. The observable outcome is that a new call site with an unseen locale variant is handled correctly because the examples carried reasoning about display versus serialization semantics, not just verbatim pairs.
The durable setup for this flow is also stored in memory rather than repeated in each prompt. Project conventions such as test location, naming style, and state ownership are recorded in CLAUDE.md as concrete rules and verified in the next session, so the iterative loop does not need to re-teach them on every turn.
Build exercise material
The reference page prescribes five build steps that make the hierarchy observable. This section turns each step into a verifiable procedure with the exact command, the observable that proves it worked, and the failure mode being avoided.
Build exercise material: Exercise 1: Observe prose variance across runs
Purpose. Demonstrate the core problem that concrete examples solve. Prose descriptions rely on interpretation, and interpretation varies across runs.
Steps.
# Pick a single transformation prompt in prose, for example:
# "Normalize the API payload: standard format, clean result."
# Run it three times with the same prompt and no examples
claude -p "Normalize this payload to standard format, clean result: { user_id: 42, created_at: '2024-01-15T14:30:00Z', profile: { display_name: 'Alex' } }"
# Capture each output to a separate file
# claude -p "same prompt" > run1.json
# claude -p "same prompt" > run2.json
# claude -p "same prompt" > run3.json
diff -u run1.json run2.json
diff -u run2.json run3.jsonObservable. Three outputs from the same prose prompt show variations in field casing, nesting depth, or timestamp formatting. The diff output is non-empty and the variations are structural, not just cosmetic. This proves that prose alone produces inconsistent results.
Build exercise material: Exercise 2: Replace prose with two to three paired examples and confirm convergence
Purpose. Show that concrete examples are the documented first-line technique for inconsistent interpretation and that the mapping generalizes.
Steps.
# Replace the prose prompt with two paired examples plus one naming sentence
# Save the new prompt to prompt-examples.md and run three timesTransform the payload by renaming snake_case to camelCase and normalizing
timestamp to ISO 8601 with milliseconds.
Input:
{ "user_id": 42, "created_at": "2024-01-15T14:30:00Z", "profile": { "display_name": "Alex" } }
Expected output:
{ "userId": 42, "createdAt": "2024-01-15T14:30:00.000Z", "profile": { "displayName": "Alex" } }
Input:
{ "order_id": 7, "created_at": null, "items": [] }
Expected output:
{ "orderId": 7, "createdAt": null, "items": [] }
Now transform:
{ "user_id": 99, "created_at": "2024-02-01T09:00:00Z", "profile": { "display_name": "Sam" } }claude -p "$(cat prompt-examples.md)" > ex-run1.json
claude -p "$(cat prompt-examples.md)" > ex-run2.json
claude -p "$(cat prompt-examples.md)" > ex-run3.json
diff -u ex-run1.json ex-run2.json
diff -u ex-run2.json ex-run3.jsonObservable. The three new outputs are byte identical or differ only in whitespace, and the previously varying fields such as createdAt and displayName are stable. A fresh test on a novel field such as session_id shows the same snake to camel rule applied without an explicit example for that field, proving generalization.
Build exercise material: Exercise 3: Test-driven iteration with happy path, edge cases, and error cases
Purpose. Show that test failures provide unambiguous signal for complex transformations and that verbatim sharing drives convergence.
Steps.
# Write tests/csv-to-json.test.ts as in Example B, covering
# happy path, null versus empty, quoted delimiter, and malformed input
npm test 2>&1 | tee test-output.txt
# Copy the FAIL block verbatim into the next prompt
claude -p "Fix the failing tests. Failures:
$(cat test-output.txt)"
npm test 2>&1 | tee test-output2.txtObservable. After the failure block is pasted, the next implementation reduces the failure count and eventually reaches zero on the explicit assertions. The failure text Expected: null preserved paired with Actual: empty string disappears after the fix, proving that the failure block was the target rather than a paraphrase. The failure boundary avoided is the proxy loop where manual prose corrections hide which branch was wrong.
Build exercise material: Exercise 4: Interview for an unfamiliar domain
Purpose. Show that the interview pattern surfaces considerations the developer would otherwise miss and that it is selected by the unfamiliarity symptom.
Steps.
# Send the interview prompt from Example C before any implementation
I need a rate limiting strategy for the API gateway. Before implementing, ask
me questions about the requirements, edge cases, and constraints I should
consider. Focus on per-user versus global scope, burst handling, reset
semantics, and failure modes.claude -p "$(cat interview-prompt.md)" > interview-questions.md
# Answer the questions, then proceed only after the answers are confirmedObservable. The model returns five to ten targeted questions about burst window, token bucket reset, zero weight handling, and shared timer state. The answers reveal at least two considerations the developer had not listed, such as the need for locale-specific display or per-user shared state, proving that the bottleneck was knowledge rather than mapping variance.
Build exercise material: Exercise 5: Batch versus sequential feedback
Purpose. Show that grouping follows dependency structure and that the wrong grouping produces incoherent fixes.
Steps.
# Intentionally introduce three interdependent defects that share normalize_applicant()
# and one independent defect in an unrelated file, then practice both modes
# Mode A: send all three interacting defects in one batched message
claude -p "$(cat batched-message.txt)" --allowedTools "Read,Edit,Write"
# Mode B: send two independent defects one at a time with verification between
claude -p "Fix the function naming: use camelCase throughout src/utils/format.ts" --allowedTools "Read,Edit"
npm run lint 2>&1 | head
claude -p "Now update the indentation to 2 spaces in the same module" --allowedTools "Read,Edit"Observable. Mode A yields a single coherent fix where the helper, its callers, and the validation step agree, and the suite passes on the batched assertion. Mode B yields two independent commits each verified before the next, with no dropped fix. The failure boundary avoided is mixing modes: batching independents risks dropping one silently, while sequencing interactives risks fixing a manifestation rather than a root cause.
Build exercise material: Exercise 6: Session hygiene and branching hygiene
Purpose. Show that transcript staleness and fork contamination are real and that clean baselining cures them.
Steps.
# After a multi-day run, start a fresh baseline seeded with durable findings
cat findings.md
cat changed.txt # list of rebuilt sources
claude "Durable findings are in findings.md. Changed sources are in changed.txt.
Re-read only those sources fresh before proposing a new timeline."
# From that clean baseline, fan out with /fork for two narratives
/fork Evaluate narrative A from the same baseline
/fork Evaluate narrative B from the same baselineObservable. Citations in the new timeline reference the rebuilt file contents rather than Day 1 contents, and the two fork results do not reference each other's reasoning. The forking lesson also flags the observable for the failure path: a resumed stale session that received only a verbal rebuild notice keeps citing old code paths.
Mechanism and API surface
- Concrete examples in context
- Two or three input output pairs placed near the task, often at system prompt end for proximity, from which the model generalises the transformation more reliably than from refined prose, with an extra edge case example only if needed.
- Test driven loop with failure sharing
- Happy path plus edge cases plus error cases as tests, run suite, share Expected X got Y failures, Claude makes targeted fixes, loop until green. Maps to evaluator optimizer with tests as evaluator.
- Interview pattern prompt
- Explicit ask me questions about requirements, edge cases, and constraints before implementing, optionally enumerating categories such as TTL, consistency, invalidation, and failure modes for the model to expand.
- Batched versus sequential feedback
- Batch in one message when fixes interact so all constraints are seen together, sequence one at a time when fixes are independent so feedback maps cleanly to code region.
- Few shot calibration versus confidence filtering
- Few shot examples showing correct judgement for ambiguous cases teach consistent decision making, while confidence thresholds are poorly calibrated and do not address inconsistent judgement at its root.
- Proximity and in context learning
- In context learning generalises from examples reliably, with best cost benefit at two to five examples, zero shot cheapest but least reliable for format sensitive tasks, examples closest to the query influence output most.
The decision rules in play
Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.
Concrete input and output examples are the first-line correction for prose that is interpreted inconsistently
The developer replaces a natural-language description of a transformation with two or three paired demonstrations showing a real input and the exact expected output for that input. The pairs are placed directly in the prompt where the task is described, so the model sees the before and after shape together. The model then copies the demonstrated mapping rather than interpreting adjectives like clean, professional, nicely, or comprehensive.
Typical placement is a short block like Input: followed immediately by Expected output: for each example, covering the common case plus one edge case. The surrounding instruction is reduced to a single sentence naming the transformation, because the examples now carry the specification.
// Input:
getUserData(userId: string): Promise<UserData>
// Expected output:
getUserData(userId: string): Promise<Result<UserData, ApiError>>// Input:
fetchOrders(customerId: string): Promise<Order[]>
// Expected output:
fetchOrders(customerId: string): Promise<Result<Order[], ApiError>>// Input payload
{
"user_id": 42,
"created_at": "2024-01-15T14:30:00Z",
"profile": { "display_name": "Alex" }
}
// Expected output
{
"userId": 42,
"createdAt": "2024-01-15T14:30:00.000Z",
"profile": { "displayName": "Alex" }
}Prose leaves mapping decisions implicit. A phrase such as normalize phone numbers or format the response cleanly does not fix whether extensions are parenthesized, whether user_id becomes userId or user_id, or whether null is preserved or dropped. The model's interpretation layer must fill those gaps, and it fills them differently on different runs or for different field shapes. Examples remove that layer entirely. Pattern matching against a concrete pair is more deterministic than linguistic inference, so cross-run variance collapses and edge-case handling becomes predictable.
Boundary. When the prose produces consistent but wrong output, more precise prose can sometimes help. When the same prose produces different wrong outputs across runs, or when two iterations fix one edge case and break another, the boundary is crossed and rewording is no longer the lever. In that region only a demonstration changes the outcome. If the developer already knows the exact transformation and only needs the model to apply it uniformly, examples are the right tool. If the developer does not know which mapping is correct and needs the model to surface hidden considerations, the opposite technique wins. That opposite case is handled under Rule 6.
Recurring specifics. Recurring signals in the tested material are interpreting your description differently each time, still produces the wrong nested structure and timestamp format, after three attempts, format cleanly, standard format, and professional and modern. Recurring correct actions are Provide 2-3 concrete input/output examples, showing the exact before and after transformation, and including the edge cases. Thresholds of 2-3 examples appear consistently; 3-5 appears when the output format has more dimensions such as tone, length, and field ordering.
Proposal. Rewrite prose with more precise language and technical terminology.
Why it attracts. it feels like the natural response to miscommunication.
Why it fails. more precise prose still passes through the interpretation layer. It narrows the space but does not eliminate ambiguity for nested structures, timestamp format strings, or field naming conventions.
When it would be right. the problem is under-specification on a single dimension that the developer fully understands and the model has shown consistent interpretation but incomplete coverage.
Proposal. Ask the model to restate its interpretation so you can find where understanding diverges.
Why it attracts. it promises a diagnostic conversation.
Why it fails. surfacing an interpretation does not fix it; the model will describe what it thinks while still lacking a concrete target to copy. Would be correct as a lightweight debugging aid before the switch to examples, not as the fix itself.
Proposal. Supply a JSON schema for the output and validate after each iteration.
Why it attracts. schema feels rigorous.
Why it fails. schema validates shape, not content mapping. A payload can satisfy the schema while still nesting timestamp fields incorrectly or dropping extensions.
When it would be right. the failure is structural drift such as missing required fields rather than inconsistent mapping logic.
Proposal. Lower temperature or raise it to explore variants.
Why it attracts. temperature is a visible knob.
Why it fails. sampling randomness is not the source of systematic misinterpretation.
When it would be right. the goal is explicitly to generate diverse candidates for human selection, not to converge on one correct mapping.
- - The base case uses two simple
TypeScriptfunction signatures and asks which technique to try first. A mutation swaps the language toPythonlogging rewrites orGostructured logging with minor-unit divisors, testing whether the solver transfers the rule across languages. - Another mutation adds that exhaustiveCLAUDE.mdprose improved simple one-to-one rewrites but left interpolated expressions andredactionwrappers inconsistent, isolating the idea that examples are specifically for the remaining edge-case variance. - A third mutation keeps prose plus aJSON schemain the prompt and shows that the schema alone did not stop format drift, reinforcing that concrete pairs dominate schema for mapping consistency.
The diagnostic signal for the examples technique is varied interpretation across repeated attempts from the same prose
The trigger is observable inconsistency. The developer describes a transformation once in natural language, runs the generation, observes an output, describes the same requirement again with slightly different wording, and receives a structurally different output. The second output may change nesting depth, timestamp formatting, field ordering, or handling of empty cells and quoted commas. After two or three such attempts the pattern of variation itself becomes the diagnosis. The remedy targets the method of specification, not the wording within that method.
When variation arises without a change in intent, the cause is ambiguous prose, not insufficient detail. Different phrasings of the same English sentence map to different latent interpretations. Adding adjectives, numbering checklist items, or spelling out format strings in prose still requires interpretation, so the variance persists. Replacing the description with a demonstration fixes the interpretation to one concrete mapping and removes the degree of freedom that created the run-to-run difference.
Boundary. If the model produces the same wrong output consistently across runs, the problem is not variance but a systematic mis-specification or a missing edge-case rule, which points toward test-driven refinement or a checklist of categories. If the model produces different outputs, the opposite case of asking for more precise prose is exactly the trap to avoid. The boundary is crossed the moment the developer can say interpreting your description differently each time.
Recurring specifics. Evidence repeats phrases such as after two prose-described attempts, three different implementations across three attempts, each interpreting the edge cases differently, and still produces the wrong nested structure and timestamp format. The expected count in the fix is consistently 2-3 examples, with concrete mention of timestamp strings, nested objects, array handling, and quoted delimiters.
Proposal. Write a JSON schema and validate after each iteration.
Why it fails. the model can comply with the schema while still choosing different internal mappings for which prose fragment maps to which schema path.
When it would be right. the observed failure is field presence or type mismatch, not interpretation drift.
Proposal. Ask the model to explain its interpretation.
Why it fails. Provides a narrative of the current misreading without anchoring future generation to a single mapping. Helpful only as a one-time diagnostic before the switch to examples.
Proposal. Run the request three times and choose the best.
Why it fails. Turns variance into a selection problem without addressing calibration, and depends on the test suite accidentally covering the ambiguous region.
- A mutation adds
nullhandling with two sub-cases,fieldpresent but empty versusfieldmissing entirely, and notes that each iteration fixes one while breaking the other. Another addsemptycells andquotedcommas as the two edge rows that must appear as paired examples before the converter is written.
Two to three well-chosen examples are sufficient to establish a pattern and enable generalization to novel cases
The developer selects a minimal example set that spans the transformation's degrees of freedom. One example covers the standard case, a second covers the most common edge case, and an optional third covers an ambiguous or compound shape. Each example is a literal Input: block paired with an Expected output: block. The model infers the underlying rule from those pairs and applies it to inputs it has not seen, including novel call-site shapes such as BigDecimal arithmetic, locale display strings, serialized API payloads, and inline log lines.
The prose around these pairs is limited to labeling which shape each pair illustrates plus the one-sentence reasoning that selects the mapping, not an exhaustive clause list.
// Example 1: standard BigDecimal case
// Input in Java
BigDecimal amount = new BigDecimal("19.99");
logger.info("amount: " + amount);
// Expected output
Money m = Money.of(amount);
logger.info("amount", m.format(locale));// Example 2: locale display edge case
// Input
displayPrice = price.toFixed(2) + " " + currency;
// Expected output
displayPrice = Money.of(price).format(locale);// Example 3: serialized payload edge case that previously went to a display string
// Input
payload.amount = bigDecimalField.toString();
// Expected output
payload.amount = Money.of(bigDecimalField).toMinorUnits();Models generalize rules more reliably from observed mappings than from stated rules. A single rule stated in English can be read multiple ways; the same rule demonstrated on two diverse inputs constrains the reading to the one mapping that fits both demonstrations. The edge-case example specifically prevents the model from inferring an under-generalized rule such as always route through display formatting.
Boundary. Adding a fourth or fifth example is justified only when the transformation has more independent axes than the three examples cover, such as when three call-site types each need representation. Beyond that, exhaustively enumerating every observed shape and matching each new file to its closest verbatim pair becomes copying rather than generalizing and hurts novel shapes. The opposite case is when new shapes keep surfacing that were never enumerated, which is the signal to include reasoning with an ambiguous case rather than to pile on more verbatim pairs.
Recurring specifics. Counts of 2-3 dominate, with 3-5 appearing for tone or style tasks and 3-4 for few-shot feedback format targets. Fields that recur as edge-case anchors are null versus empty, quoted delimiters, duplicate headers, and timestamp format strings.
Proposal. Provide an exhaustive clause list covering every observed call-site shape with explicit precedence.
Why it attracts. completeness
Why it fails. generalize to shapes not yet listed. It reduces conflict rate without eliminating it and leaves the model to guess on the next novel shape.
Proposal. Attach the same example set drawn from common sites that already convert cleanly.
Why it fails. examples from the non-diverging cases do not teach the diverging ones. The fix must draw from the currently diverging types and include feedback from actual wrong outputs.
Proposal. Demand 20 examples for coverage.
Why it fails. quality dominates quantity and large example sets increase prompt length without improving coverage of the critical axes.
- A mutation introduces an ambiguous fourth shape deliberately to test generalization, noting that a model that only copies pre-specified cases fails on it while a model shown reasoning generalizes. Another mutation notes that earlier instruction paragraph iteration lowered divergence rate but did not make three site types behave uniformly, isolating the claim that iteration on examples beats iteration on prose.
Test-driven iteration is the primary technique for complex transformations with many edge cases
The developer writes the expected behavior as an executable test suite before asking the model to implement or refine. The suite covers the happy path, edge cases, and malformed input handling, then the developer shares the failing test output verbatim with the model. The loop repeats, each iteration sharing the current failure list until all tests pass.
Sharing those two blocks as the next user message is the mechanism. The developer does not paraphrase the failures into prose about null handling philosophy.
// tests/migration.test.ts
describe("migration handles null values", () => {
it("preserves null in output JSON", () => {
const input = { name: "Ada", nickname: null };
const result = transform(input);
expect(result.nickname).toBeNull();
expect(result.name).toBe("Ada");
});
it("handles missing field versus empty field distinctly", () => {
const withEmpty = { tag: "" };
const withMissing = {};
expect(transform(withEmpty).tag).toBe("");
expect(transform(withMissing).tag).toBeNull();
});
});FAIL: testMigrationHandlesNullValues
Expected: null preserved in output JSON
Actual: null replaced with empty string ""
FAIL: testHandlesQuotedDelimiter
Expected: row with quoted comma yields two fields ["a, b", "c"]
Actual: row split into three fields ["a", " b", "c"]A complex transformation has too many interacting rules to hold in a prompt. Tests make each rule explicit and machine-checkable with no interpretation step. The failure output Expected X, got Y is unambiguous and localized, so the model can target the exact assertion that failed rather than guessing which prose clause maps to which symptom.
Boundary. If the transformation is a single mechanical mapping with consistent misinterpretation but few edge cases, examples alone are lighter and faster. The boundary toward tests is crossed when the prose or examples describe the mapping correctly yet behavior under burst handling, token bucket reset, zero weights, or negative amounts still fails. The nearby opposite case is relying on a specification document and asking the model to check each clause before finishing, which misses edge cases not enumerated in prose.
Recurring specifics. Recurring suites include CSV parsing with quoted delimiters and multiline fields, rate limiter with burst and token bucket, payment validation with zero and negative amounts, migration scripts with null versus empty, and payment modules with idempotency window expiry. Recurring workflow phrases are write the tests first, share the test failures, and iterate until all tests pass.
Proposal. Describe all edge cases in a single prompt and request implementation and tests together in one pass.
Why it fails. speed but conflates specification and implementation, so tests match what was built rather than what should be built.
When it would be right. tests are generated independently before implementation exists.
Proposal. Generate an implementation first and then generate tests to match it.
Why it fails. Reverses the test-first guarantee and produces tests that encode the current mistakes.
Proposal. Implement first without tests, then ask the model to review its own output.
Why it fails. review against the same ambiguous description tends to validate the model's own interpretation rather than an external oracle.
- One mutation frames the collaboration as a repeated loop where business people create a project, developers implement, and testers run
pytest, and the fix is to formalize that loop as a named slash command so the sequence is consistently invoked. Another frames therate limiterwithburstandresetedge failures automatically surfaced rather than discovered after manual testing, testing recognition of the workflow that surfaces failures without manual inspection.
Tests must be written first and failures shared verbatim to give unambiguous Expected versus Actual feedback
The workflow encodes three invariants. First, the test suite is authored before or alongside the specification, covering happy path, edge, and malformed cases. Second, the suite is executed and its output is copied exactly, including the FAIL: line, the Expected: line, and the Actual: line. Third, each refinement turn treats a failing assertion as a blocker that must be fixed before the next change proceeds, continuing until all tests pass.
That output block, not a paraphrase such as null handling is still wrong, is what is pasted back.
$ npm test 2>&1 | head -n 40
FAIL src/transform.test.ts
- handles null versus empty distinctly
Expected: null preserved in output JSON
Actual: null replaced with empty string ""
- handles quoted delimiter
Expected: ["a, b", "c"]
Actual: ["a", " b", "c"]
Tests: 2 failed, 18 passed, 20 totalExpected: null preserved versus Actual: empty string leaves no interpretive gap. It names the field, the desired value, and the observed value. The model can map the failure to the specific branch that produced the replacement and fix it without guessing which English sentence about null semantics was intended. Paraphrased feedback reintroduces ambiguity and invites the model to patch a different branch than the failing one.
Boundary. Sharing failures is most powerful when an oracle exists that can say Expected and Actual. When no oracle exists because the requirement has multiple defensible interpretations and the deciding rule is not in the source, such as legacy.acct_type -> tier collapsing eight codes into four, an after-the-fact loop cannot recover the absent intent. That region calls for surfacing interpretations as confirmed input and output pairs before committing rather than appending validation errors after the run.
Recurring specifics. Failure messages in the tested material follow the pattern FAIL: testMigrationHandlesNullValues with Expected: null preserved and Actual: null replaced with empty string, or Expected: X, got Y for token bucket reset. The discipline phrase run the existing test suite after each code change, treat a failing test as a blocker recurs across rate-limiter and idempotency validator cases.
Proposal. Manually run tests and paste failures without automated enforcement.
Why it fails. Works once but lacks the discipline that each change is gated on passing tests before surfacing output. A manual loop depends on the developer remembering to gate each iteration.
Proposal. Write all edge-case tests up front, generate once, and declare complete regardless of whether they pass.
Why it fails. writing tests is not the same as iterating against their failures.
Proposal. Enable plan mode and have the model propose a validation algorithm in prose before any implementation.
Why it fails. Useful for a different class of problem but does not substitute for the machine oracle that makes refinement unambiguous.
- A mutation adds
currencyrounding andlocaleformatting as the two non-schema edge failures that must be shared together when they share the idempotency store path, while keepingmalformed JSONseparate. Another mutation replacesnullinJSONwithidempotencykeys arriving within the same millisecond, testing whether the solver recognizes the same feedback discipline across domains.
Interview pattern is the primary technique for unfamiliar domains where the developer does not know what to specify
Before any code is written, the developer instructs the model to ask clarifying questions that surface requirements, edge cases, and constraints the developer may not have anticipated. The prompt explicitly names the interview pattern and lists the dimensions to probe. The model then produces a set of targeted questions, the developer answers them, and only then does implementation begin.
The response to the first prompt typically asks What invalidation strategy do you need: write-through, write-behind, or cache-aside, What consistency guarantee is required, What happens when cache and database diverge, How should concurrent invalidation requests behave, and What is the deployment topology.
I need a caching layer for the API. Before implementing, ask me questions
about the requirements, edge cases, and constraints I should consider.
Focus on invalidation strategy, TTL policy, consistency requirements,
and failure modes.I need an audit logging system for a healthcare compliance domain.
I understand the technical options but not the regulatory requirements.
Before proposing a design, ask me clarifying questions about data
retention policies, access control needs, and compliance constraints
so we surface considerations I might miss.When the developer lacks domain expertise, the bottleneck is unknown unknowns, not ambiguous wording. No amount of concrete examples can demonstrate a requirement the developer never thought to include. The interview pattern inverts the flow so the model, which has broad domain knowledge, pulls the missing dimensions into the conversation before code commits to a shape that omits them. This prevents the failure where the first implementation compiles and passes basic checks yet later review finds cache invalidation on writes, partial failure handling, or per-user Redis shared state was never specified.
Boundary. If the developer already knows the exact transformation and is struggling only with consistent application, the interview pattern is the wrong tool and examples dominate. The boundary is marked by phrases such as you suspect there are failure modes you have not considered, I am not an expert in this domain, and repeated rephrasing without improvement. The nearby opposite case is provide 3-5 concrete input and output examples for the charge processing function showing zero amounts or duplicate idempotency keys, which is correct when the mapping is known and only its demonstration is missing.
Recurring specifics. Recurring interview prompts include Before implementing, ask me questions about the requirements, edge cases, and constraints, ask clarifying questions about cache invalidation strategies, TTL policies, consistency requirements, and failure modes, and ask about delivery channels, event types, and urgency thresholds. Recurring domains are caching layer, healthcare audit logging, payment processing, notification system, and authentication.
Proposal. Provide concrete input and output examples of the desired log format.
Why it fails. Correct when the output shape is known but inconsistently applied. Fails when the missing piece is regulatory policy or invalidation semantics that no example set can invent.
Proposal. Use plan mode to explore the codebase and propose architectures.
Why it fails. Addresses complexity and implementation scope, not domain knowledge gaps. It surveys files but does not surface regulatory or invalidation requirements that live outside the codebase.
Proposal. Write the test suite first and iterate by sharing failures.
Why it fails. Effective when the desired behavior is already specified and the goal is to drive implementation toward passing tests. Fails when the tests themselves would omit the same edge cases the developer never considered.
Proposal. Generate three alternative implementations and pick one.
Why it fails. Defers the decision to implementation time and relies on a generic approach that may not fit the specific consistency or compliance need.
- One mutation adds
idempotencykey collisions andcurrencyrounding as the hidden edge cases the interview must surface before tests are written. Another swapscaching layerforrate limiting for the API gatewaywith per-user versus global scope, testing whether the solver maps the same pattern to a different infrastructure choice.
The selection among examples, test-driven iteration, and interview depends on which symptom is present
The three techniques form a hierarchy whose selection key is the symptom.
- Symptom prose interpreted differently each time selects concrete input and output examples first. - Symptom many edge cases and complex transformation selects test-driven iteration with verbatim failure feedback. - Symptom working in an unfamiliar domain and suspecting missed considerations selects the interview pattern before any code.
The developer classifies the current stall by one of those symptoms and reaches for the matching technique without mixing them.
Each technique repairs a different failure source. Examples repair an ambiguous specification where the model lacks a concrete mapping to copy. Tests repair a complex behavior space where manual inspection cannot cover combinations. Interview repairs a knowledge gap where the specification itself is incomplete. Using the wrong repair wastes iterations: more prose does not add coverage, more tests do not invent unknown requirements, and more questions do not stabilize a mapping that is already known.
Boundary. The hierarchy has an order. Examples are the first response to inconsistency before heavier test suites. Tests are the heavier response when examples would need to enumerate dozens of behaviors. Interview is the precursor when the developer cannot yet name the behaviors that tests or examples should cover. The opposite case to each is the temptation to apply the technique that is lighter or more familiar, such as adding another paragraph of prose to a caching design that actually needs an interview.
Recurring specifics. Evidence repeats a table-like mapping: Prose description interpreted differently each time -> Concrete input and output examples, Complex transformation with many edge cases -> Test-driven iteration, Working in an unfamiliar domain -> Interview pattern, Multiple issues that affect each other -> Batch feedback, Multiple independent issues -> Sequential feedback. The phrase most effective for inconsistent interpretation is the anchor for examples, most effective for complex transformations for tests, and most effective for unfamiliar domains for interview.
Proposal. Use the interview pattern to have the model ask questions before implementing a known date-parsing library.
Why it fails. the symptom is test-verifiable edge handling, not domain ignorance. Tests dominate there.
Proposal. Write a test suite first and iterate by sharing failures for a vague notification system.
Why it fails. the suite would encode the same incomplete assumptions about delivery channels and urgency.
Proposal. Try plan mode for every refinement.
Why it fails. plan mode addresses cross-file scoping and strategy selection, not the three specific stalls this hierarchy covers.
- Mutations shuffle the domain while holding the symptom constant, for example moving the interview signal from
cachingtopayment processingtonotification channels, or moving the test-driven signal fromrate limitertoCSVparser tocurrencyconverter, testing whether the mapping is symptom-driven rather than domain-driven.
Prose rewording, temperature changes, and additional instruction detail do not cure inconsistent interpretation
When examples are indicated, the tested material repeatedly rejects three familiar fixes. Rewriting the prose with more precise language retains the interpretation layer. Changing temperature alters sampling randomness, not mapping determinism. Adding more adjectives, longer checklists, or a thicker system prompt still tells the model what to do rather than showing it what the output looks like. The correct move is to replace abstract adjectives with paired demonstrations.
Each rejected fix operates one level removed from the cause. Prose precision asks the model to infer better but still to infer. Temperature asks it to sample differently but still from the same ambiguous distribution. Longer instructions ask it to follow more rules but each rule still requires interpretation. Only a demonstration moves the signal from interpreted instruction to copied pattern, which is why 2-3 concrete pairs outperform any amount of additional wording.
Boundary. The anti-pattern label is bounded to the inconsistency symptom. If the problem is under-specification of tone where clean or professional is legitimately open to taste, a single brand-voice example sentence can still be the lightest fix. Temperature or instruction refinement would be correct if the goal is explicitly to generate diverse drafts for selection, not to converge on one mapping.
Recurring specifics. Rejected moves appear as Rewrite the prose description with more precise language and technical terminology, Increase the temperature parameter so the model explores a wider range, Add a detailed system prompt listing every possible style attribute, and Increase the CLAUDE.md file size with additional stylistic adjectives. Accepted move appears as Replace abstract prose instructions with concrete examples, which give a more reliable signal than qualitative adjectives.
Proposal. Lower temperature to make outputs more deterministic.
Why it fails. Helpful for reducing sampling variance but does not teach a mapping the model has not seen. It makes the same misinterpretation more repeatable.
Proposal. Add a retry loop that re-submits responses failing a regex.
Why it fails. Adds enforcement without teaching. It catches some drift but does not show the correct transformation.
- A mutation notes that a senior engineer suggests the problem is different mental models of
comprehensiveand still proposes prose refinement, forcing recognition that aligning mental models requires demonstration, not redefinition.
Examples beat exhaustive prose checklists, schema validation, and ask-for-interpretation detours
Three specific alternatives are repeatedly rejected when examples are indicated. A numbered checklist of test categories such as null inputs, boundary values, and error paths still requires judgment about what each category means for a specific function. A JSON schema validates field presence while leaving nesting and timestamp format decisions to interpretation. Asking the model to restate understanding surfaces the current reading without anchoring the next generation to a target. The winning move remains paired input and expected output examples that show field names, nesting depth, array handling, and format strings directly.
Each alternative preserves one interpretive step. The checklist tells the model to cover a category but not how that category manifests in this function. The schema tells it what shape is valid but not how to map the source shape to that target shape. The restatement tells the developer what the model thinks but does not give the model a pattern to match. Examples collapse all three gaps in one demonstration.
Boundary. Checklists and schemas become valuable complements after examples have fixed the mapping, for example as a validation gate that rejects regressions. They are not substitutes for the initial mapping lesson. The opposite case where asking for an interpretation is useful is as a one-time diagnostic before the switch to examples, not as the refinement itself.
Recurring specifics. Checklist items recur as null inputs, boundary values, error paths, concurrency, schema mentions recur as JSON schema for the output, and interpretation prompts recur as Ask Claude to restate its current interpretation. The winning phrasing recurs as Concrete input-to-output examples eliminate interpretive ambiguity entirely.
Proposal. Validate against a schema after each iteration.
Why it fails. Provides a safety net after the mapping is correct but does not teach the mapping itself.
Proposal. Ask for three style variants and vote.
Why it fails. Useful for discovering preferences but expensive and still depends on the model guessing which variant is intended.
- One mutation keeps the checklist in the prompt and shows it improves coverage unevenly, then still requires examples to stabilize
assertionstyle andedge casehandling, reinforcing that the checklist is insufficient alone.
Batched single-message feedback is required when fixes interact through shared state or output shape
When multiple corrections touch the same contract, the same helper, or the same tool result, they are described together in one detailed user message before the model produces the next revision. The message names each defect, shows how they share a boundary, and asks for a single revised version that reconciles them. The model therefore sees all interacting constraints at once and can design fixes that are mutually consistent.
A more entangled instance is a single get_ranked_results() function where cache key, sort tie-break, and error swallowing each change an input the others depend on. The developer lists all three defects in one message with the failing integration test and asks the model to reason about their mutual effects before producing one revised function.
Three changes needed. They interact with each other.
1. Error responses must include an `error_code` field.
2. Logging must include the `error_code` in structured format
via `logger.info` with key `error_code`.
3. The client SDK type definitions in `types.ts` must reflect
the new `error_code` field.
Please address all three together and ensure the response shape,
the log line, and the SDK types agree.Fixing interacting issues one at a time creates misleading intermediate states. The fix for null handling changes which policy_id values exist at sort time, the tie-break fix depends on whether errored lookups are excluded or zero-filled, and the error-handling fix alters what gets cached. If the model fixes one, the manifestation of the next is already shaped by the previous patch, so it corrects a symptom of the interaction rather than the root cause. Batching lets it reason about the dependency graph once and produce a coherent patch set rather than three patches that happened to converge.
Boundary. Batching is the wrong move when issues are independent. Sending unrelated defects together can dilute attention and cause the model to skip one. The opposite case, fixing each defect in a separate sequential message and confirming after each, is correct precisely when the tested material says fixing each one in isolation changes how the others manifest is absent. Evidence signals for batching are phrases such as they interact with each other, every quote path calls this helper before validation, and changing the error handling pattern also affects the logging format.
Recurring specifics. Recurring batched clusters are error_code in response plus logger plus types.ts, normalize_applicant() with policy_id stripping, prior_claims null, and hyphenated surnames, transaction lock plus stale cache sharing a state variable, and cache key plus sort ordering plus error handling in one function. Shared helper names such as normalize_applicant() and shared timer instances are the tell.
Proposal. Fix them one at a time.
Why it attracts. Correct for independent bugs.
Why it fails. each fix targets the manifestation left by the prior patch.
Proposal. Send all three together regardless of interaction.
Why it fails. Over-batches independent work. Correct only when interaction is established.
Proposal. Pick the most severe bug only.
Why it fails. Leaves coupled defects unresolved.
- One mutation puts three entangled defects in one function to test batching. Another spreads them across two files plus a
READMEtypo.
Sequential single-issue feedback is required when fixes are independent and isolated
When defects are located in different files, touch different tool results, or alter unrelated field names, each is sent in its own message and verified before the next is addressed. The loop is fix one -> re-run the suite or inspection -> confirm -> fix the next.
A CI review bot that flags an off by one error in pagination.ts and a null check in an unrelated notification/handler.ts also belongs here.
First iteration: Fix the function naming. Use `camelCase` throughout
in `src/utils/format.ts`. Wait for result.
Second iteration: Now update the indentation to use `2 spaces` in
the same module. These two changes do not affect each other.Independent fixes have no shared contract to reconcile, so mixing them forces the model to maintain two unrelated mappings in one generation step and increases the chance it silently drops one. Sequential fixing keeps attention focused, makes each change independently reviewable, and prevents a correct pagination fix from being associated with a skipped null check. The total latency of separate turns is offset by the clarity of verification.
Boundary. The boundary is crossed the moment a fix for one changes the manifestation of another. If the tested material says fixing the first bug changes second bug manifestation, sequential delivery is the trap. The nearby opposite case is any evidence where the model bundled independent issues last sprint and produced a correct fix for one while silently skipping the other. That history is the signal to unbundle.
Recurring specifics. Phrases such as naming convention issue and indentation issue do not affect each other, off by one in pagination and missing null check in notification handler in a different file, and typo in one error string and missing unit test for expired licenses recur as markers of independence. The wait for result step appears consistently.
Proposal. Always deliver every issue in a single message.
Why it fails. Ignores cost and causes the observed pagination fix landing while null check is skipped.
Proposal. Always deliver one at a time.
Why it fails. Correct here but fails as a universal rule.
Proposal. Deliver in severity order.
Why it fails. Does not address coupling.
- One mutation flips the teamโs current practice to force recognition of reverse guidance.
Mixed dependencies are solved by fixing the blocking schema or contract issue first, then batching the independents
When a set contains both a blocking change and several independents, the workflow is two-phase. First, the blocking field or schema fix is sent alone and verified. Once that contract is correct, the remaining independent fixes are sent together because they now share the corrected contract but not each other.
Phase 1. Fix the output JSON schema. Change the field name from
`usr_name` to `user_name` in the formatter in `src/format.ts`.
Confirm the schema before proceeding.
Phase 2. Now fix the two independent issues together against the
corrected schema. Date parsing must handle `timezone` offsets
in `src/parse.ts` and currency formatting must use locale `de-DE`
in `src/money.ts`.The schema field name is load-bearing. Both date parsing and currency formatting must conform to the output shape that contains it. If all three are batched, the model may shape the two value fixes to the old field name and then need a second pass. If all three are sequenced individually, the two independent value fixes are unnecessarily serialized. The two-phase hybrid respects the dependency graph.
Boundary. If no field or contract blocks the others, the first phase is unnecessary and the whole set belongs in Rule 11. If every remaining fix touches the same helper, the second phase itself belongs in Rule 10 and even the independents should be batched from the start.
Recurring specifics. The canonical three-issue case is date parsing mishandles timezone offsets, currency formatting uses the wrong locale, and output JSON schema has an incorrect field name where the statement Issue 3 changes the output shape both must conform to recurs. Another canonical case is idempotency window failures grouped with a malformed JSON response where the window pair shares store logic.
Proposal. Send all three together.
Why it fails. Wastes chance to verify contract first.
Proposal. Send each separately.
Why it fails. Serializes two fixes that could be batched after the blocker.
Proposal. Use interview pattern.
Why it fails. Addresses knowledge gaps, not a blocking contract.
- One mutation swaps
dateandcurrencyforretry backoffplusjob lockingsharing atimer.
Structured feedback after a stall must state exact current behavior, exact desired behavior, and a concrete failing case
After two or three stalled cycles with casual language such as make it better, the next message is a structured triad: exact current behavior, exact desired behavior, and a concrete failing case.
Current: `transform(record)` where `record.nickname` is `null`
returns `{ "nickname": "" }`.
Desired: preserve `null` as `{ "nickname": null }`.
Failing case:
Input: { "name": "Ada", "nickname": null }
Expected: { "name": "Ada", "nickname": null }
Actual: { "name": "Ada", "nickname": "" }Vague feedback gives no localized target. The triad names the field, shows the value difference, and provides a reproducible case so the model can fix that branch.
Boundary. On the first iteration a short instruction may suffice. After three rounds of revision the output still does not match, the triad is the lever. Try again without context never provides a target.
Recurring specifics. Phrases such as After three refinement cycles, output still does not match requirements, has been asking for changes in casual language without specific details, and Provide a structured specification: exact current behavior, exact desired behavior, specific test case that should pass recur. For the React case the triad is missing error state, add an error prop (boolean) that renders a red ErrorBanner from ./components/ErrorBanner when true.
Proposal. Switch to a different model.
Why it fails. the stall is caused by insufficient feedback specificity, not by model capability tier.
Proposal. Start over with a completely new approach.
Why it fails. Discards accumulated context that still holds value and does not cure the feedback vagueness.
Proposal. Add more examples to the original prompt and re-run.
Why it fails. Useful for first attempts but does not address the specific gaps in the current output after a stall. The fix must name what is wrong now.
- One mutation swaps
Reacterror handling fornullpreservation inJSON, testing whether the triad transfers across UI and data tasks. Another frames the stall asadd more coverageproducing regressions, where the triad becomes the concrete expected behaviors and edge cases as a targeted suite.
Interruption is the correct mid-task correction when the agent broadens scope beyond the intended boundaries
If the agent edits beyond scope, for example applying validation to all API endpoints when only one category was intended, the developer interrupts with Esc and provides a narrower instruction mid-task naming the specific category.
Stop the broad edit. Only add validation to endpoints under
`src/api/public/` that use `createOrder`. Leave `src/api/internal/` unchanged.Letting a broad edit complete wastes time and can introduce hard-to-revert bugs. Mid-task correction keeps context and lets the model narrow the Grep pattern immediately.
Boundary. Interruption is for scope drift during execution. If the whole task was wrong, a full revert and rewrite may be appropriate. Let the agent finish then manually revert is always less efficient when the error is caught early.
Recurring specifics. Phrase The developer wants to give more specific guidance mid-task and option Interrupt and provide more specific instructions recur. Esc is the concrete command.
Proposal. Let the edit finish then revert manually.
Why it fails. Wastes the broad pass.
Proposal. Undo all and start over.
Why it fails. Loses established context.
Proposal. Stop using the agent.
Why it fails. Abandons automation when correction would suffice.
- One mutation frames adding tests to
200 files, where mapping withGlobandGrepbeats interruption.
Rewriting the original prompt is preferred over patching after repeated patch failures that leave regressions
When two or three patches each fix one defect while reintroducing another, the developer stops appending corrections and rewrites the original request as one consolidated prompt stating the full interacting constraints.
For a CSV migration, the rewritten prompt becomes Convert CSV to JSON: empty cell -> "", quoted comma -> keep intact, duplicate header -> append _2. For null versus missing, it becomes missing tag -> null and empty tag -> "" with examples.
A thread dominated by failed attempts fills context with contradictory reasoning. Rewriting presents the combined truth once so the model does not anchor on which fix broke which other. It prevents each iteration fixes one scenario while breaking the other.
Boundary. Rewriting cures a patch spiral where corrections interact. If defects are independent and each patch held, sequential patching preserves verified work. The opposite is preserve the working parts, target only what needs improvement for isolated defects.
Recurring specifics. Phrases each iteration fixes one scenario while breaking the other and provide 2-3 concrete input and output examples recur. The null versus empty versus missing triad recurs.
Proposal. Break into two sequential iterations.
Why it fails. Helps when fixes are independent but fails when they share a helper.
Proposal. Switch to plan mode.
Why it fails. Adds overhead without fixing the inconsistent prompt.
Proposal. Ask the model to self-review.
Why it fails. Validates against the same ambiguous description.
- One mutation shows
CLAUDE.mdprose improving one-to-one rewrites while edge cases stay broken, another shows natural-language guidance reducing but not stopping wrong-block edits, isolating that the remaining cure is a rewritten prompt with examples, not more guidance.
Session continuation with `--resume` preserves conversation history but does not refresh stale tool results
The developer uses claude --resume breach-timeline to continue a named session that previously catalogued indicators in 22 files. The flag restores the assistant and tool_result history so follow-up questions see prior analysis. However, file contents captured as tool_result values are a snapshot from the time of the original Read. Resuming does not re-read those files. If the platform rebuilt 7 of those files between sessions, the resumed context still contains the old contents unless the developer explicitly re-reads them or starts fresh.
The effective pattern for stale evidence is to start a new session seeded with a structured summary of durable findings plus an explicit list of changed sources for fresh analysis, rather than relying on verbal notice that files 3, 7, and 11 were rebuilt.
claude --resume breach-timeline
# In the resumed session, explicitly refresh changed files
# Read the rebuilt files so the new contents replace the stale snapshotTool results are inline history, not live views. The model cites what it saw, not what the filesystem currently holds. Verbal notice without a fresh Read leaves the old text deterministically in context, so timeline entries keep referencing code paths no longer present. A new session with injected summary plus fresh reads for the changed files gives the model both the durable baseline and the current truth without the stale payload.
Boundary. --resume is ideal when prior context is still mostly valid and the developer wants to continue the same thread, compare prior review findings, or keep accumulated decisions. When core evidence has changed, such as source documents replaced and a web index refreshed in a research pipeline, the boundary is crossed and resume with a change notice is insufficient. The opposite correct move there is Start a new session, provide a structured summary of durable findings, and explicitly identify changed sources.
Recurring specifics. Recurring flags are claude --resume, claude -p for headless, --allowedTools for pre-approval, and /compact for compression. Recurring counts are 22 files catalogued, 7 rebuilt, and 9,000 files in the investigation. The failure phrase kept citing the Day-1 contents recurs.
Proposal. Resume the named session with --resume, then re-read only the 7 rebuilt files leaving the other 15 cached results intact.
Why it fails. it seems surgical, but stale tool results cannot be evicted piece-wise by verbal announcement and the resumed session still carries the old conversation's framing.
Proposal. Resume, supply a structured change summary, then call fork_session from the resumed session.
Why it fails. fork_session only copies the baseline as it exists. If the baseline is contaminated with stale contents, both forks inherit them. The baseline must be clean before branching.
Proposal. Start two fresh sessions manually providing a summary.
Why it fails. Works but loses the rich detail of the original analysis unless the summary is structured with file locations and severity.
- One mutation makes the
CIre-review re-flag already-fixed issues as unresolved when the only instruction isre-review the PR. Another replaces the investigation with a market analysis where web index refresh requires a fresh session before branching.
Context degradation in long sessions is solved by externalizing findings and running `/compact`, not by accumulating summaries inside the growing conversation
During a multi-hour exploration the agent's tool outputs, especially verbose Read dumps, fill the context window and push early specific findings such as KafkaSourceAdapter, LateArrivalReconciler, and RetryingBatchWriter out of effective attention. The agent begins describing a typical adapter-to-writer pipeline instead of citing exact classes, and two consecutive questions produce contradictory wiring diagrams. The correction has two parts. Persist confirmed findings to a findings.md scratchpad on disk and consult it before answering, and run /compact to compress verbose discovery output while keeping the externalized findings authoritative.
A second Read result is trimmed to only the relevant class definitions before it enters context.
# findings.md - confirmed discoveries, high signal only
- `KafkaSourceAdapter` in `src/ingest/kafka.ts` -> calls `RetryingBatchWriter.writeBatch()`
- `LateArrivalReconciler` in `src/reconcile/late.ts` -> wires to `scheduler/hooks`
- `RetryingBatchWriter` in `src/write/batch.ts` -> retry on `ETimedOut` only# In the session, after context fills
/compactThe live conversation is itself the degrading medium. Keeping a running discovered components block inside the conversation merely adds more tokens to the same window that is already saturated, so the block itself gets buried. Externalizing to a file separates authoritative state from transient discovery, and /compact clears the heavy dumps while the file survives as a fresh re-injection.
Boundary. Summarizing older confirmed entries into a compact synopsis inside the conversation is the near-miss. It reduces tokens but compresses specific class names away into typical patterns and reintroduces drift, especially under position effects where late tokens dominate. Keeping all raw Grep and Read results so the agent retains maximal raw context is the opposite extreme and accelerates degradation. The balanced point is compact findings plus trimmed Reads.
Recurring specifics. Phrases such as context is dominated by verbose file dumps, describe the usual reconciliation step, two consecutive questions produced contradictory wiring diagrams, and the instruction always reference the exact classes you discovered earlier that sharpens briefly then drifts again recur. The findings.md scratchpad and /compact pair recurs as the fix.
Proposal. Have the agent maintain a discovered components block inside the live conversation and re-inject it at the top of every question.
Why it fails. the block grows with the conversation and still competes with heavy dumps.
Proposal. Summarize older entries inside the conversation into a synopsis.
Why it fails. summarization compresses the specific class names that are load-bearing for correctness.
Proposal. Keep exploration in the main session, letting Grep and full-file Read results accumulate for cross-checking.
Why it fails. accumulation is the direct cause of attention dilution.
- One mutation isolates verbose discovery in a
subagentper reporting domain and emphasizes that the boundary between the subagent and main must pass only a compact structured finding, not the raw edge list, otherwise isolation is lost.
Forking from a shared baseline is the mechanism for exploring divergent approaches without cross-contamination
From one base session that has already loaded an OpenAPI spec, a migration map, and an architecture analysis, the lead calls fork_session to create parallel branches. Each branch inherits the same baseline but evolves independently. One branch assesses credential rotation, the other session invalidation. Neither branch's reasoning appears in the other.
When a branch later needs to be revisited after it has accrued new decisions, the correct move is not to resume the stale branch but to begin a new session seeded with a structured summary of that branch's accumulated decisions plus the shared baseline.
Shared baseline: OpenAPI spec + migration map + 2,000 line analysis
|
+-- fork_session -> Branch A: credential rotation narrative
|
+-- fork_session -> Branch B: session invalidation narrative
Compare branches after both complete.Sequential exploration in the same session contaminates the second approach with residual context from the first. Fresh sessions that repeat the baseline waste effort rediscovering the spec and risk inconsistent starting points. fork_session gives identical starting state without recomputation and keeps divergence isolated by design.
Boundary. Forking assumes the baseline is clean and current. When the baseline itself is contaminated with stale file contents, forking preserves the contamination in each copy. That region requires a fresh baseline built from a structured summary before forking. The opposite distractor is to run each scheme as separate Task subagents from a coordinator without passing the full baseline, where subagents never inherit context and therefore start without the shared spec.
Recurring specifics. Calls of fork_session appear across research pipelines, market report structures with geography versus industry segments, and legacy authentication module refactoring as extract microservice versus refactor in place. The phrase Shared context from yesterday and full conversation history as the inherited baseline recurs.
Proposal. Resume yesterday's session to explore the first approach, then start a new session for the second manually recreating context.
Why it fails. Asymmetric and biased because the second loses rich detail.
Proposal. Resume the session twice in sequence with fork_session after resuming a stale session.
Why it fails. Only masks drift because the stale baseline is copied into both forks.
Proposal. Spawn each scheme as a Task subagent with a concise system prompt naming its scheme.
Why it fails. subagents start with blank context. They need the complete established decisions explicitly injected in the spawning prompt, not just a name.
- One mutation replaces research narratives with containment narratives, another replaces
APIschemes with report structures, and a third adds the requirement that early strong designs keep diverging on sharedpaginationanderror envelopecontracts, testing whether the solver recognizes that fork alone does not enforce shared contract consistency.
Bulk mechanical work is made idempotent and scripted with `claude -p` and pre-approved tools, not carried in one long interactive session
For a 1,800 module migration, the developer generates a file list, then loops calling claude -p once per module with --allowedTools pre-approving Read, Edit, and Write. Each invocation starts with a fresh context window, so no run accumulates hundreds of files' worth of reads and edits. A subtle prompt flaw is caught by refining the prompt on the first two or three files before launching the full batch.
For large refactoring that ended mid-way through 5,000 files, the companion discipline is to write a script that checks which files still lack type hints and passes only the unprocessed files to the next run, making re-runs idempotent.
# Generate the list of modules needing migration
cat > /tmp/list.txt <<'EOF'
src/handlers/payment.ts
src/handlers/refund.ts
EOF
# Loop once per module with a fresh context
while IFS= read -r f; do
claude -p "Migrate $f from deprecated SDK to replacement SDK using the confirmed wrapper. Only touch $f." \
--allowedTools "Read,Edit,Write,Bash"
done < /tmp/list.txtA single interactive session concentrates the whole job into one context window, and model performance degrades as context fills. Headless claude -p trades one long window for many short windows, each bounded to one file. --allowedTools grants approval so the loop runs unattended without interactive prompts, while noting that it grants rather than restricts the overall tool surface.
Boundary. This pattern is for mechanical, well-understood transformations applied file by file. When each request touches an unpredictable set of files and subtasks cannot be enumerated before seeing the request, the pattern shifts to orchestrator-workers rather than a fixed file list loop. The opposite attractors re-run the same command and let the model skip already modified files and increase max_turns fail because the model does not automatically detect completed files and longer runs compound context pollution.
Recurring specifics. Recurring flags are claude -p, --allowedTools, and --resume. Recurring counts are 1,800 Python modules, 5,000 files, 40 files in a migration, and 80 files where a single iteration produces inconsistency. The instruction refine the prompt on the first two or three files before launching the full batch recurs.
Proposal. Open a single long interactive session and instruct the model to migrate them one by one.
Why it fails. Fails on context accumulation and requires a human in the loop.
Proposal. Use the /loop command in an interactive session.
Why it fails. Repeats a prompt inside one session and inherits the same context-accumulation problem without scriptability.
Proposal. Add the complete step-by-step migration to CLAUDE.md.
Why it fails. Loads every session with a long procedure, bloats context, and does not itself execute any migration.
- One mutation adds
Editfailures on files with near-identical response-construction lines and asks whether to anchor on unique signatures withReadthenWritefallback, testing the deterministic editing discipline alongside the loop structure.
Iterative loops must drive continuation from the deterministic `stop_reason` field, not from assistant text or fixed ceilings
Each turn the loop inspects stop_reason on the response. While it is tool_use the loop appends each tool_result back into the conversation and continues. When it is end_turn the loop terminates. A high iteration ceiling remains only as a runaway safety net, not as the primary stop condition. Tool results are appended so the next decision reflects the latest QC state.
// Pseudocode for the loop controller
let history = [initialPrompt];
while (true) {
const response = await callClaude(history);
if (response.stop_reason === "tool_use") {
const results = await executeTools(response.tool_calls);
history.push(response, { role: "user", tool_results: results });
continue;
}
if (response.stop_reason === "end_turn") {
break;
}
if (iterations++ > SAFETY_CEILING) {
escalateToHuman(history);
break;
}
}Assistant text such as alignment finished cleanly is a phrase inside the generation and can appear mid-pipeline for both high-quality and low-quality samples. Terminating on that text truncates legitimate long analyses and does not stop re-alignment loops where the text never appears. A fixed cap such as 5 iterations cuts off complex tickets mid-resolution even though the model would have continued gathering information. Only stop_reason is a deterministic signal controlled by the runtime about whether the model requested another tool turn.
Boundary. The ceiling is still needed as an upper bound against runaway loops that repeat the same re-alignment call, but it is secondary. The opposite incorrect move is to replace the open adaptive loop with a fixed sequential pipeline of align_reads -> call_variants -> annotate -> qc_report that always executes four stages, which removes adaptivity to read depth and contamination flags that vary per sample.
Recurring specifics. Evidence repeats stop_reason, tool_use versus end_turn, SAFETY_CEILING as a net, and failure phrases alignment finished cleanly, pipeline complete, and analysis finished. Tool names align_reads, call_variants, annotate, and qc_report recur.
Proposal. Terminate as soon as a response contains a recognized completion phrase such as pipeline complete.
Why it fails. completion language can appear before the pipeline is genuinely complete and varies with wording guidance.
Proposal. Replace the open loop with a fixed four-stage pipeline.
Why it fails. Removes the need to decide continuation but cannot choose each next tool from the previous result based on read depth.
Proposal. Decide the next tool by passing the latest result to a separate planner sub-call instead of appending the tool result to the conversation.
Why it fails. Poor placement of the decision and still relies on stop_reason incorrectly.
- One mutation lowers the ceiling and begins truncating legitimate long analyses.
Large-scale refactoring avoids single-pass execution and is broken into reviewed iterative steps or per-file scripted invocations
A 35 file or 50 file migration that is attempted as refactor all files in one iteration produces inconsistent changes and quality degradation from attention dilution and context load. The correction is iterative decomposition. Either refactor file by file or module by module with reviews between iterations, or script a loop that invokes claude -p once per file with --allowedTools.
Iterative approach for 50 files in module
For each module in dependency order
Run Glob to list files
Refactor the batch
Review diff against the agreed wrapper and argument order
Append confirmed decisions to the shared summary before next batchSingle-pass loads many files' worth of Read and Edit outcomes into one window, so early decisions about wrapper form and argument order are crowded out by late-file content. Inter-iteration reviews externalize those decisions and reset attention. Per-file claude -p keeps each context bounded to one file.
Boundary. Increasing thinking time for a comprehensive simultaneous refactor is the near-miss. It deepens reasoning but does not change the fact that the model is still doing one job across 50 files in one window. Multiple parallel instances each handling a subset without shared context is also a miss because it produces inconsistent patterns across subsets.
Recurring specifics. Counts 35 and 50 files, phrases refactor all 35 files and refactor all 50 files, and the fix Iterative approach: refactor file by file or module by module with reviews between iterations recur.
Proposal. Increase thinking time for comprehensive simultaneous refactoring.
Why it fails. Deeper thinking does not fix cross-file consistency when the shared pattern is lost to context load.
Proposal. Provide detailed specifications upfront enabling single-pass refactoring.
Why it fails. Specification helps but cannot hold consistency across 50 files in one window.
Proposal. Multiple parallel instances each handling a subset independently.
Why it fails. Produces divergence without shared context about the intended wrapper.
- A mutation replaces the iterative fix with
/looporCLAUDE.md, testing that the loop must be a script outside the single session.
When prose and iterative feedback have plateaued on unseen shapes, examples that carry reasoning plus an ambiguous case teach generalization
In a Money value type migration, call sites are heterogeneous: raw BigDecimal arithmetic, locale-aware display strings, serialized API payloads, and inline log lines. Narrow prose clauses reduced conflict rate but did not eliminate it, and each new shape triggers fresh divergent transformations. The fix is few-shot pairs that include both the before and after and the reasoning that selects each mapping, plus one deliberately ambiguous case where the reasoning must choose between display and serialization semantics.
// Example A: locale display, reasoning selects format path
// Input
priceDisplay = amount.toString() + " USD";
// Reasoning: display path, locale sensitive, route through Money.format
// Expected output
priceDisplay = Money.of(amount).format(locale);
// Example B: serialized payload, reasoning selects minor units path
// Input
payload.amount = amount.toString();
// Reasoning: serialization path, preserve numeric precision, route through Money.toMinorUnits
// Expected output
payload.amount = Money.of(amount).toMinorUnits();
// Example C: ambiguous inline log line
// Input
logger.info("amount " + amount);
// Reasoning: inline interpolation is display-like but inside a log line, prefer format for human readability
// Expected output
logger.info("amount", Money.of(amount).format(locale));When failures cluster on shapes never enumerated, copying pre-specified cases cannot cover the next unseen shape. Reasoning attached to each example lets the model generalize the selection rule to novel patterns rather than matching each new file to its closest verbatim example. The ambiguous case explicitly teaches the disambiguation boundary.
Boundary. A fixed set of verbatim pairs for each observed shape works when the migration will not encounter new shapes. The opposite case where prose is still the cure is when the migration's divergence rate is still high on common shapes that are already enumerated.
Recurring specifics. Shapes BigDecimal arithmetic, locale display, serialized payload, and inline log line recur with instruction phrase replace ad-hoc formatting with Money.of and route display through Money.format.
Proposal. Keep refining prose with an exhaustive clause per observed shape and an explicit precedence list.
Why it fails. Reduces but does not eliminate conflicts and leaves each fresh shape to trigger a new round of divergence.
Proposal. Add a fixed set of verbatim pairs for each observed shape and match each new file to its closest example exactly.
Why it fails. Solves observed shapes but fails on novel ones.
Proposal. Provide one example per shape and interview each file's intended numeric versus display semantics.
Why it fails. Asks for per-file confirmation that does not scale and still requires generalization.
- A mutation adds three narrow clauses and shows conflict rate drops but persists, isolating reasoning-rich examples as the remaining cure.
Discovery in unknown codebases uses `Grep` to find an entry point and follows imports with offset `Read`, never bulk `Read` of whole directories
In a 1,400 file monolith with no architecture docs, the agent converges on the discount logic by grepping for discount symbols and price calculations, reading only the matching functions with offset reads, then following their imports and call sites with further targeted Grep and reads to trace into dependent modules. It does not Glob every file under checkout and Read each in full, nor does it Grep every pricing file and Read every hit in full before reasoning.
# Converge pattern for an unfamiliar codebase
grep -r "discount" --include="*.ts" .
# Read only the matching function with line offsets
# Then follow imports from that hit
grep -r "import.*pricing" --include="*.ts" .Bulk reading saturates context with unrelated code before the relevant modules are identified, causing the agent to lose track of earlier files and propose edits in the wrong module. Grep-driven entry plus import-guided tracing keeps context proportional to the task and preserves attention for the actual dependency chain from checkout handler to pricing service to shared utilities.
Boundary. When the target set is known and small, reading those files in full is appropriate. When the set is unknown and large, enumeration before reasoning is the trap. The opposite case of adding read efficiently to the system prompt trims some reads but still front-loads large sets before any concrete lead, so it is graded as insufficient.
Recurring specifics. Tools Grep, Glob, Read with offset, and Bashsed recur. Patterns checkout handler, pricing service, and shared utility modules recur. Phrase never upfront-read whole files before you have a concrete lead recurs.
Proposal. Use Glob to enumerate every source file under the target modules then Read each in full.
Why it fails. Saturates context before any lead.
Proposal. Read each module in fixed batches summarizing every batch before loading the next.
Why it fails. Still loads many files before discovery.
Proposal. Grep every pricing file then Read each matching file in full to compare.
Why it fails. Better but still reads whole files rather than offset ranges around the hit.
- One mutation makes the symbol non-unique as
timeoutinsidesocket_timeout, testingGrepplus unique-anchorEditwithReadandWritefallback.
Evaluation suites must mirror production input distribution and explicitly include messy variants, not just expand clean cases
A prompt iterated against 800 FAQ-derived cases that are cleanly written can pass evals while production traffic with typos, long rambling multi-part questions, and irrelevant personal details still fails. The fix is to sample recent production queries and add cases that mirror those inputs, including the messy variants, then rerun the suite to measure real performance. Expanding with thousands more clean variants or raising the accuracy threshold does not move the metric that never observed the failing behavior.
An eval score estimates production performance under a sampling assumption. When the sample is systematically different from production, the estimate is biased. Only changing the composition of the test set makes the iterate-and-ship loop sensitive to the failures users report.
Boundary. When the suite already mirrors the true distribution and still shows high scores while complaints rise, the remaining lever may be human grading nuance or latency versus cost tradeoffs, but the first lever is always composition. The opposite case of freezing the suite at launch and expanding clean volume only tightens confidence intervals around the wrong quantity.
Recurring specifics. Recurring composition notes are typos, rambling multi-part questions, irrelevant details, and edge cases such as typos, overly long or rambling user input, and irrelevant information. Counts 800 test cases appear as the example distribution mismatch.
Proposal. Raise the minimum accuracy threshold.
Why it fails. Demands more of a metric that never observes the failing behavior.
Proposal. Replace automated grading with human expert grading.
Why it fails. Changes how each case is scored without changing which cases exist.
Proposal. Expand to several thousand additional cleanly written variants.
Why it fails. Increases volume without expanding coverage of messy inputs.
- One mutation keeps the clean suite and shows guidance to include messy variants.
Feedback grouping for test failures follows root cause, not file count or severity ordering
Three tests fail: two trace to the same idempotency store logic and a third asserts malformed JSON returns 400. The two sharing the store are reported together in one message with the store interaction named, and the JSON failure is reported separately because it does not interact. Severity ordering is not the determinant.
Batched report for shared root cause, one message
Two failures trace to the same store logic in `src/idempotency/store.ts`
- testIdempotencyKeyDedup: duplicate webhook with same idempotency key
Expected: second delivery returns 200 with cached response
Actual: second delivery processed again and created duplicate
- testWindowExpiry: idempotency window expiry
Expected: key released after 24h
Actual: key still present after 24h and blocks retry
Separate report for unrelated failure, second message
- testMalformedJsonReturns400
Expected: 400 on malformed payload
Actual: 500 internal errorRelated failures share a fix. Addressing one without the other produces an incomplete patch that still fails the second test. Mixing them with an unrelated JSON failure obscures the root-cause relationship and risks a conflated fix that targets the symptom of the interaction. Severity ordering would place a criticalJSON fix first even though the two idempotency failures must be understood together.
Boundary. If all three failures share one root cause, all three belong in one message. If each has a distinct cause, each belongs alone. The opposite incorrect move is to report all three individually even when two share the store logic.
Recurring specifics. Recurring phrasing is Effective iterative refinement feedback groups related failures by root cause and separates unrelated issues. File paths such as src/idempotency/store.ts and status codes 400 recur.
Proposal. Report the simplest failure first for momentum, then batch the shared ones.
Why it fails. Momentum ordering does not address the coupling and can interleave an unrelated JSON fix before the store logic is reconciled.
Proposal. Report all three in separate messages for modular debugging.
Why it fails. Keeps debugging modular but lets the store fixes re-break each other.
Proposal. Report all three together regardless of interaction.
Why it fails. Inflates the single message with an independent JSON concern.
- One mutation swaps
idempotencyforretry backoffsharing atimerinstance, another swapsmalformed JSONfor alog levelone-word typo, testing that root-cause grouping is invariant.
Writing and review phases should be isolated so the reviewer does not inherit the generator's intent bias
A 300 line module is generated in one session and then reviewed in the same session. The review returns looks good with minor style suggestions but misses two logic errors that a human catches. The correction is to use a second independent instance for review that receives only the code, not the generation reasoning. The reviewer then reads the code cold.
The same instance reviews what it intended to write, not what it actually wrote. Its context contains justifications such as I used null return here because edge case X that make the erroneous branch feel intentional. An isolated reviewer has no such memory and evaluates the branch on its observable behavior.
Boundary. Isolation is for objectivity in review, not for all multi-file work. When the review is a self-check with clear criteria and the cost of a second instance exceeds the benefit, a self-review pass can suffice. When the output is a draft that measurably improves through critique cycles against explicit criteria, the related but distinct pattern is evaluator-optimizer looping rather than a single isolated pass.
Recurring specifics. Phrases reviews intent, not output and reviews as a stranger reading it cold recur, with 300 line module as the canonical size where bias manifests.
Proposal. Limit code generation to smaller chunks so the review has less to process.
Why it fails. Helps context size but does not remove intent bias.
Proposal. Add more explicit review criteria to the same session.
Why it fails. Improves coverage but still reviews through the lens of the original reasoning.
Proposal. Switch both phases to the same model tier.
Why it fails. Tier alignment does not address the bias mechanism.
- One mutation adds adaptive thinking and asks whether raising
effortreplaces the isolation, testing recognition that reasoning depth does not remove intent bias.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| Concrete examples | Test driven iteration | Examples show the pattern via pairs, test driven shares failures as evaluator feedback. Examples address interpretation variance for simple shapes, tests address complex logic with edge cases and validation. |
| Concrete examples | Interview pattern | Examples are for when the developer knows the exact transformation but the model misinterprets it. Interview is for unfamiliar domains where the developer might miss hidden considerations. Different problem, different technique. |
| Batched feedback | Sequential feedback | Batched is for fixes that interact where the model needs all constraints at once. Sequential is for independent fixes where batching would confuse which feedback applies to which region. |
| Prose refinement | Examples | Refined prose still relies on interpretation and does not close interpretation room. Pairs eliminate interpretation room entirely and are the exam answer for runs that diverge for the same prose. |
| Few shot for judgement | Confidence threshold filtering | Few shot showing correct judgement for ambiguous cases teaches consistency. Confidence thresholds are poorly calibrated and do not address the root cause of judgement inconsistency. |
Traps
The tempting answer. Rewrite the description with more precise language because better wording feels more thorough.
Why it fails. More precise prose still relies on interpretation, so variance persists across runs. The exam marks examples as the first line fix for inconsistent interpretation precisely because pairs generalise more reliably than wording.
What is correct. Provide two or three input output pairs, verify generalisation on a new case, and add an edge case pair only if edge handling still misses.
The tempting answer. Use concrete examples for an unfamiliar caching domain because examples feel universally helpful.
Why it fails. Examples help when the developer knows the exact before and after shape, interview helps when the developer does not yet know which requirements matter. Swapping them misses hidden tradeoff discovery in unfamiliar domains.
What is correct. Use the interview pattern for unfamiliar domains to surface requirements and constraints, use examples when the desired transformation is known but misinterpreted.
The tempting answer. Raise a confidence threshold so inconsistent judgement calls are suppressed when below a score.
Why it fails. Thresholds are poorly calibrated and do not teach the decision principle, so inconsistency persists at a different cutoff. Few shot examples showing correct judgement for ambiguous cases directly teach the principle.
What is correct. Add few shot examples with reasoning for judgement calls, not bare pairs but pairs plus the rationale, so the model generalises the decision, not just surface matching.
The tempting answer. Batch every piece of feedback together to save turns because batching feels faster.
Why it fails. Batching independent issues confuses which feedback applies to which code region, while batching interacting issues is what benefits from seeing all constraints together. The exam tests the interacts versus independent split.
What is correct. Batch when fixes interact, sequence when they are independent, and gate the choice on whether changing one fix area affects another.
The tempting answer. Add many more examples to ensure coverage because more examples feel more thorough.
Why it fails. Two to three well chosen examples covering standard and edge already set the pattern, many shot adds token cost without proportional reliability, and the exam frames two to five as the best ratio for most tasks.
What is correct. Keep examples to two or three plus at most one edge case, then verify on a fresh case rather than piling on more pairs.
The tempting answer. Continue steering with prose or pairs even though a test suite with clear failures is available.
Why it fails. Test failures are the most unambiguous feedback possible with no interpretation room, leaving them unused wastes deterministic leverage that prose or pairs cannot match for edge case consistency.
What is correct. When a suite exists, share the failure output as the steering signal and let the model target the named assertion, iterating until green.
CI/CD Integration
Run Claude Code headlessly with deterministic transcripts and structured findings so a pipeline can block, post inline comments, and preserve context without hanging or repeating noise.
What you need to know
In CI Claude Code stops being an interactive assistant and becomes an automated check or generation step that must run without a keyboard, produce output a machine can parse, and leave the repository in a predictable state. The single most directly tested fact is the -p flag, also --print, which switches the session from interactive expecting input to print mode that processes the prompt, emits the result to stdout, and exits. Without it any CI job invoking claude with a review or generation prompt hangs forever waiting for input that never arrives, and flags that do not exist such as CLAUDE_HEADLESS=true or --batch or stdin redirection from /dev/null are the distractors.
Headless output must be machine parseable because no human watches the run, findings must be consumed to post inline PR comments, update dashboards, or trigger downstream gates. --output-format json wraps the run in an envelope with result text, session_id, and total_cost_usd, and --json-schema validates the final output against JSON Schema draft-07 in print mode only, exposing the validated object in structured_output that CI can extract with jq dot structured_output. The distinction the exam tests is deterministic versus probabilistic enforcement, --json-schema validates against the schema with an internal re prompt on mismatch, while prompt only system prompt instructions to output a shape may drift under tool pressure or long context.
Separation of generation and review context is a correctness requirement in pipelines that both generate and review code. The session that generated a middleware carries the reasoning why that approach was chosen, and asked to review its own output in the same session it is measurably less likely to question its own decisions. The fix is independent review instances, one print mode invocation to generate, a separate print mode invocation to review with no access to the generation session reasoning, which evaluates the code on its own merits and produces more thorough findings than self review.
Incremental review preserves signal to noise across pushes, because without memory of prior findings every run analyses the PR from scratch and re derives the same five issues on every push even after the developer fixed three and deliberately deferred two. A context free re scan cannot distinguish new problems from known deferred ones, so it repeats comments and erodes trust. The fix is to store previous findings as a JSON artifact, include them in the next run's context, and instruct Claude to report only new or still unaddressed issues so fixed items drop out and only fresh or persistent problems reappear.
Two supporting controls complete the headless surface. CLAUDE.md loads in CI just as interactively, so documenting testing standards, fixtures such as test slash factories and test slash fixtures plus test slash setup slash db dot ts, and review severity criteria there lets headless test generation use project patterns rather than generic boilerplate. --permission-mode selects autonomy, default with prompts that abort on unapproved action in -p, acceptEdits to auto accept filesystem edits in the working directory for auto fix jobs, dontAsk to auto deny anything not allow listed for read and report, bypassPermissions to skip prompts entirely in trusted sandboxed runners, and plan for read only headless planning, with --max-turns as a cost bound safety net rather than the primary completion control which remains stop_reason and with Message Batches API as the 50 percent savings path only for latency tolerant overnight reports, not for blocking pre merge checks that need synchronous results within seconds to minutes.
Headless flags and their exact contract
-p is the non interactive gate, it switches Claude Code to print mode where there is no interactive UI, no human approval prompts so anything not explicitly allowed or auto approved aborts the run, no follow up conversation unless resumed with --continue or --resume, and stdin is capped at 10MB as of Claude Code v2.1.128 so larger inputs must be written to a file and referenced in the prompt. --bare is a minimal mode that skips hooks, MCP servers, settings, auto memory, and CLAUDE.md so the same command produces the same result on every machine, reached for when a fast predictable scripted run is needed without project configuration.
--output-format controls transcript shape, text for humans, json for envelope with result, structured_output, total_cost_usd, and session_id, stream-json with --verbose and --include-partial-messages for NDJSON whose last line has type result to surface progress in CI logs rather than blocking silently. --json-schema requires --output-format json and Claude Code v2.1.205 or later, validates the final output against draft-07 before exit with internal re prompt on mismatch, and exposes the validated object in structured_output.
System prompt shape and permission modes for CI
Four system prompt flags exist and the choice determines which default guidance stays. --system-prompt replaces the entire default prompt, --system-prompt-file replaces with a file, --append-system-prompt appends to the default, --append-system-prompt-file appends a file, with append keeping default tool guidance, safety instructions, and coding conventions while replace drops them and should only be used when the pipeline identity differs from Claude Code's coding assistant role.
--permission-mode offers six values. default with standard checking that aborts on unapproved in -p, acceptEdits that auto accepts edits and common filesystem commands in the working directory for lint fix jobs, plan that enforces read only exploration for risk reports, dontAsk that auto denies anything not allow listed for locked down read and report, bypassPermissions that skips prompts in trusted sandboxes and can be disabled org wide, and the general principle of least privilege where only needed tools are allowed via --allowedTools or --disallowedTools rather than over permitting for convenience.
Output validation and where batch fits
Prompt only schema enforcement via --append-system-prompt Never run destructive commands is probabilistic and may drift under tool pressure, long context, or adversarial tool output, while --json-schema is deterministic with validation plus internal re prompt before exit. The correct headless gate is the deterministic one, prompt restrictions are the wrong answer when the stem requires a guarantee that destructive commands are blocked.
The Message Batches API is a cost optimization path with hard constraints, 50 percent savings versus synchronous, up to 24 hour processing window with no SLA and most batches finishing within an hour, no multi turn tool calling within a single batch request, and custom_id correlation for pairs. The matching rule is synchronous for blocking workflows such as pre merge checks where someone is waiting, batch for latency tolerant overnight reports and weekly audits where results are consumed later.
Review isolation and feedback hygiene
Session context isolation governs review quality. Generating in session A and reviewing in session B as two separate claude -p invocations removes generation reasoning from the reviewer's context and yields more thorough findings than self review in a single session, which the exam presents as the correct fix for a pipeline that generates then reviews with the same context.
Incremental review hygiene governs trust. Storing prior findings as an artifact and re including them with an instruction to report only new or still unaddressed issues prevents the duplicate comment failure where fixed issues reappear and known deferred issues are unnecessarily republished on every push, which developers stop reading when it repeats.
Authoritative mechanism reference
The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.
Mechanism reference
This section documents every mechanism the task touches, with exact flag and field names, the full value space where it exists, and the boundary behavior that turns a correct configuration into a failed run.
Mechanism reference: 3.6.1 The -p and --print non-interactive switch
Claude Code ships as an interactive terminal program. Invoked as claude with no mode flag it enters a conversational loop: it renders a rich terminal interface, waits for keyboard input, prompts for tool approvals, and keeps the session open for follow-up turns. A pipeline runner has no terminal and no human, so that loop blocks on a read that never arrives and the job eventually times out.
The -p flag, long form --print, switches to single-shot print mode. In this mode the process does three things differently. It reads the prompt from its positional argument and, if data is piped on stdin, includes that input up to the current cap, which the lesson set places at 10 MB as of the documented version. It runs without any interactive interface: no prompts, no progress bars, no colorized output. It writes the final response to stdout once the turn completes and exits with a process status the pipeline can gate on.
The forensics file flags an implicit single-turn claim. The correct statement is that -p runs until the turn completes, which may involve multiple agentic turns internally, bounded by --max-turns if set. The CLI does not promise that print mode is always one model invocation; it promises that the process terminates after producing its result, without waiting for further human input.
Distractors that look like fixes but are not documented include environment variables such as CLAUDE_HEADLESS=true or CLAUDE_MODE=CI, flags such as --batch, --non-interactive, --headless, --pipeline, --silent, --quiet, --yes, --no-confirm, --review, and stream tricks such as < /dev/null. None of these are the documented non-interactive switch. The only correct spellings are -p and --print.
# WRONG: interactive mode in a runner with no operator, hangs until timeout
claude "Review this pull request for security issues"
# CORRECT: non-interactive print mode, writes to stdout and exits
claude -p "Review this pull request for security issues"
# CORRECT: pipe a diff into print mode, still non-interactive
git diff origin/main...HEAD | claude -p "Review this diff for regressions"
# CORRECT: cap cost and keep pipeability with explicit limits
cat build-error.txt | claude -p --max-turns 5 "Concisely explain the root cause of this build error" > output.txtWhat this proves: the -p switch is the only documented gate between interactive and headless execution. The observable output is a terminating process that writes to stdout; without -p the observable is a hang or timeout. The failure boundary is any unattended invocation without -p.
Mechanism reference: 3.6.2 Output format surface: text, json, stream-json
The --output-format flag controls the shape of what print mode writes to stdout. It accepts three values:
textis the default. The final response is plain text. Human-readable and suitable when a person will read the log. No envelope, no cost metadata.jsonemits a single JSON envelope after the run finishes. The envelope carries the answer inresultplus machine fields such assession_idandtotal_cost_usd. This is the correct choice when an automated consumer must parse the result, log spend, or post findings programmatically.stream-jsonemits a sequence of JSON objects as the run progresses. Each object is a delta or event, not a complete document. The final envelope still appears, typically as a line withtypeequal toresult. A consumer that needs live progress, a tailing log, or partial rendering should use this mode; a consumer that only acts after completion should usejson.
A common mistake is to pass --json or --stream as standalone flags. The documented spellings are --output-format json and --output-format stream-json. Another is to treat a stream-json run as one JSON document: it is a stream of objects separated by newlines, so parsing requires line-delimited JSON handling.
With --output-format json the stdout is a single JSON object. With --output-format stream-json combined with --verbose and --include-partial-messages the stdout is newline-delimited JSON where text deltas can be reassembled from events whose shape includes type stream_event and a payload field often named delta with type text_delta. The exact stream envelope field names beyond that should be confirmed against the current headless documentation when building.
Envelope fields that recur in the lesson set are result for the free-text answer, structured_output for the validated object when a schema is supplied, session_id identifying the run, total_cost_usd with a per-model breakdown that is an estimate and may differ from the billed amount, and subtype which signals error variants such as a schema-retry exhaustion. The forensics file asks for exact result field names; these are the names the lesson set documents and the headless docs confirm in part.
Mechanism reference: 3.6.3 Input format surface: text and stream-json
The --input-format flag mirrors the output flag on the input side. It tells the CLI how to consume the prompt it is given:
textis the default. The prompt is a single string argument, optionally augmented by piped stdin.stream-jsonlets the caller feed a stream of JSON input events. This suits drivers that already produce structured deltas, such as a generator that emits one record per changed file.
The two flags are independent. A run can take text input and produce stream-json output or vice versa. Setting --input-format stream-json does not fix a hang; interactivity is still governed by -p.
Mechanism reference: 3.6.4 Schema-constrained results with --json-schema
CI consumers rarely want free-form text. They want a typed contract they can gate on: a verdict enum, a list of findings with file and line, a reasons array. The --json-schema flag supplies that contract as a JSON Schema, validated against draft-07. The CLI validates the final output against the schema before exit, re-prompts internally on mismatch, and exposes the validated object in structured_output inside the json envelope.
The schema flag has three hard requirements that are tested as traps. It only applies in print mode. It requires --output-format json. In the lesson set it is documented as requiring Claude Code v2.1.205 or later. Prompt-only JSON instructions without the flag are probabilistic and may drift; the flag is the deterministic enforcement.
Details that matter at the boundary:
- The schema string is JSON. It must declare types, required fields, enums, and
additionalPropertieswhere strictness is wanted. Theformatkeyword is accepted as an annotation but is not enforced. - On success, the envelope contains both
resultandstructured_output; the latter is the extracted typed object. On schema-retry exhaustion the envelope may lackstructured_outputor carry asubtypeindicatingerror_max_structured_output_retries. A gate that only checks the exit code can let a contract violation pass; it must also assert the presence ofstructured_output. - Extraction is
jq '.structured_output'or a stricter assertionjq -e '.structured_output'. Parsingresultis the wrong field for the typed contract. - Streaming and schema interact independently. Schema validation governs the final shape;
stream-jsongoverns delivery. Neither removes the need for-p.
# Schema-validated contract: typed verdict with reasons, deterministic gate
claude --bare -p "Classify this diff as pass or fail" \
--output-format json \
--json-schema '{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail"]},"reasons":{"type":"array","items":{"type":"string"}}},"required":["verdict","reasons"],"additionalProperties":false}' \
| jq -e '.structured_output' > /dev/null || exit 1What this proves: the pair --output-format json plus --json-schema moves from probabilistic instruction to CLI-enforced contract. The observable output is a typed structured_output object whose absence must fail the build.
Mechanism reference: 3.6.5 Non-interactive invocation with output format and tool restriction
This is the first of the required substantial examples. It shows a complete headless invocation that is non-interactive, machine-parseable, and hard-bounded to a minimal tool surface for a read-only audit. It combines -p, --output-format json, and --tools so the run can never write even if the prompt asks it to.
#!/usr/bin/env bash
set -euo pipefail
# Example 1: non-interactive invocation with output format and tool restriction
# A read-only security sweep that can only report, never modify.
# Uses -p for headless, --output-format json for machine parsing,
# and --tools to remove write capability entirely.
DIFF_FILE="$(mktemp)"
git diff origin/main...HEAD > "$DIFF_FILE"
claude -p \
--output-format json \
--permission-mode dontAsk \
--tools "Read,Grep,Glob" \
--max-turns 8 \
"Review the diff at $DIFF_FILE for common vulnerability patterns. \
Do not modify any file. Return findings as plain text summary in result."
# Gate on exit code, then extract the parseable result
echo "Exit code: $?"
jq -r '.result' result.json 2>/dev/null || cat result.jsonWhat this proves: the pipeline terminates deterministically because -p is present. The "Read,Grep,Glob" restriction is a hard boundary: the agent cannot invoke Write or Edit even if prompted to do so, unlike an advisory instruction in CLAUDE.md which a model can drift past. The dontAsk mode is chosen here because the run should only ever read and report; any write would need to fail the build rather than prompt.
Mechanism reference: 3.6.6 Schema-validated result contract
This is the second required substantial example. It shows a schema-validated result contract where the payload must appear in structured_output with exact field names the downstream step can gate on.
// verdict.schema.json - typed contract for a blocking gate
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"verdict": { "type": "string", "enum": ["pass", "fail"] },
"reasons": { "type": "array", "items": { "type": "string" } },
"findings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"file": { "type": "string" },
"line": { "type": "integer", "minimum": 1 },
"severity": { "type": "string", "enum": ["low", "medium", "high", "critical"] },
"message": { "type": "string" }
},
"required": ["file", "line", "severity", "message"],
"additionalProperties": false
}
}
},
"required": ["verdict", "reasons", "findings"],
"additionalProperties": false
}#!/usr/bin/env bash
set -euo pipefail
# Example 2: schema-validated result contract
# The validated object appears in .structured_output, not at the envelope root.
# The gate must assert that field, not just the exit code.
SCHEMA='{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail"]},"reasons":{"type":"array","items":{"type":"string"}},"findings":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"line":{"type":"integer","minimum":1},"severity":{"type":"string","enum":["low","medium","high","critical"]},"message":{"type":"string"}},"required":["file","line","severity","message"],"additionalProperties":false}}},"required":["verdict","reasons","findings"],"additionalProperties":false}'
git diff origin/main...HEAD | claude --bare -p \
--output-format json \
--json-schema "$SCHEMA" \
--max-turns 10 \
"Review this diff for security issues and return verdict, reasons, and findings" \
> gate-result.json
# Deterministic gate: fail closed if the validated object is missing
jq -e '.structured_output' gate-result.json > /dev/null || {
echo "Gate failed: structured_output missing or invalid" >&2
cat gate-result.json >&2
exit 1
}
# Further gate on typed verdict
jq -e '.structured_output.verdict == "pass"' gate-result.json > /dev/null || {
echo "Gate failed: verdict is not pass" >&2
jq '.structured_output' gate-result.json >&2
exit 1
}
# Emit findings for the next step
jq '.structured_output.findings' gate-result.json > findings.json
echo "Findings extracted to findings.json"What this proves: the contract is draft-07 and is validated before exit. Success is signaled by a present structured_output; the failure mode is a non-zero exit or a missing field or a subtype indicating exhausted retries. The observable for a downstream step is the typed findings array with file, line, severity, and message.
Mechanism reference: 3.6.7 Pipeline workflow file with gating on exit status
This is the third required example. It shows a generic pipeline workflow file that runs a blocking check, parses the JSON envelope, and gates the pipeline on both the process exit status and the presence of the validated payload. No vendor-specific runner syntax is assumed beyond a standard workflow file with named steps and shell commands.
# pipeline-workflow.yaml - generic CI workflow with deterministic gating
# Describes a pipeline that blocks on a Claude Code review step
name: pr-review-gate
on: pull_request
jobs:
review:
runs-on: pipeline-runner
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- name: Check out repository
run: checkout --depth full
- name: Run Claude Code review with typed contract
run: |
set -euo pipefail
SCHEMA='{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail"]},"reasons":{"type":"array","items":{"type":"string"}}},"required":["verdict","reasons"],"additionalProperties":false}'
git diff origin/main...HEAD | claude --bare -p \
--output-format json \
--json-schema "$SCHEMA" \
--permission-mode dontAsk \
--max-turns 10 \
"Review this PR for security and correctness. Return verdict and reasons." \
> result.json
# Gate 1: process exit status - the shell already fails on non-zero due to set -e
# Gate 2: schema contract - structured_output must be present
jq -e '.structured_output' result.json > /dev/null || {
echo "ERROR: structured_output missing" >&2
cat result.json >&2
exit 1
}
# Gate 3: typed verdict must be pass
jq -e '.structured_output.verdict == "pass"' result.json > /dev/null || {
echo "FAIL: verdict is not pass" >&2
jq '.structured_output' result.json >&2
exit 1
}
echo "Gate passed"
# Persist for next step
cp result.json review-artifact.json
- name: Post inline findings
if: always()
run: |
set -euo pipefail
# Example consumer: iterate findings and post at file and line
# Findings posting is driven from structured_output, not from free text
jq -c '.structured_output.findings[] // empty' review-artifact.json | while read -r finding; do
file=$(echo "$finding" | jq -r '.file')
line=$(echo "$finding" | jq -r '.line')
msg=$(echo "$finding" | jq -r '.message')
echo "Posting comment on $file:$line - $msg"
# post-comment --file "$file" --line "$line" --body "$msg"
done
- name: Log cost
if: always()
run: |
jq -r '"Cost: \(.total_cost_usd // 0) session: \(.session_id // "unknown")"' review-artifact.jsonWhat this proves: the workflow file gates on three signals that together are sufficient. The process exit code is the first gate, the presence of structured_output is the second, and the typed verdict is the third. The workflow also demonstrates cost logging from total_cost_usd and session tracing from session_id, and shows that posting relies on the typed fields rather than on regex over result.
Mechanism reference: 3.6.8 Turn and cost limits
Headless runs can loop over tools until the model decides it is done. In an open-ended task that bound is not implicit; the pipeline must set it. The --max-turns <n> flag caps the number of agentic turns, then the process exits. This bounds both cost and wall-clock time. The --verbose flag emits full turn-by-turn output for debugging a failing job without changing the pass or fail semantics.
The envelope field total_cost_usd is the CLI's client-side estimate with a per-model breakdown. It may differ from the billed amount and should be treated as an approximate spend signal for dashboards, not as an invoice figure.
Mechanism reference: 3.6.9 Permission modes available unattended and their risk
When -p is active there is no human to answer a permission prompt. Any tool call that would have triggered a prompt needs a pre-decided policy, or the run aborts. The --permission-mode flag sets that policy for the invocation. It applies only to the current run and does not modify settings.json on disk.
The documented modes that appear in the lesson set are:
defaultruns standard permission checking. In-pmode any unapproved action aborts the run because no one can answer a prompt. Rarely useful alone in CI.acceptEditsauto-accepts file edits and common filesystem commands inside the working directory. Suitable for auto-fix jobs such as lint fixes and formatting where edits are expected.dontAskauto-denies anything not explicitly allowed viapermissions.allowor the built-in read-only command set. This is the locked-down baseline for read-only CI that should only report, never write.bypassPermissionsskips permission prompts entirely. Functionally equivalent to--dangerously-skip-permissionsas a standalone flag. Reserved for trusted, sandboxed environments and can be disabled org-wide via a managed setting.planis read-only exploration: no edits or commands execute. Suitable for generating a plan or risk report without touching the repository.
The forensics file flags an open question on a read-only flag versus a planning permission mode. The documented read-only control is --permission-mode plan, not a standalone --read-only flag. The lesson set describes plan as the mode that makes the session read-only; mentions of --read-only as a literal flag in evidence should be treated as not independently confirmed against the current CLI reference and answered as plan where the exam asks for the read-only mode.
Tool-surface flags compose with the mode:
--allowedTools "<rules>"lists tools that run without a prompt. It grants approval, it does not shrink the surface. A broad allowlist without deny rules still permits any granted tool to run.--disallowedTools "<rules>"denies named tools.--tools "Bash,Edit,Read"restricts which built-in tools are available at all. These form a hard boundary: a tool not in the available set cannot be invoked even if the model requests it.--add-dir <path>grants file access in an extra directory. It does not automatically bring in that directory'sCLAUDE.md; loading extra-directory memory is gated by the environment variableCLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1.--model <alias|name>selectssonnet,opus, or a full model id for the headless session.--bareskips discovery of hooks, skills, plugins, MCP servers, auto memory, andCLAUDE.md, for a fast predictable scripted run.
The risk escalates from plan and dontAsk at the low end to bypassPermissions at the high end. The least-privilege rule is to grant the narrowest approval that unblocks the pipeline: allowlist the specific tool via --allowedTools or a permissions.allow rule, keep everything else behind normal checks, and never feed untrusted input to a run with permissions skipped.
Mechanism reference: 3.6.10 Unattended permission and secret configuration
This is the fourth required example. It shows a settings file and shell setup for unattended execution with deny rules that survive permissive modes and with secret handling that never commits keys.
// .claude/settings.json - project-level unattended policy
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Read(**/*)",
"Glob(**/*)",
"Grep(**/*)",
"Bash(git diff:*)",
"Bash(git log:*)"
],
"ask": [
"Edit(**/*)",
"Write(**/*)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(sudo *)",
"Bash(chmod 777 *)",
"Bash(git push --force*)",
"Bash(git push:*)",
"Bash(npm publish*)",
"Bash(curl * | sh)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "scripts/policy-check.sh" }
]
}
]
}
}#!/usr/bin/env bash
set -euo pipefail
# Example 4: unattended permission and secret configuration
# Demonstrates the deny-survives-mode pattern and protected secret injection.
# Secret handling: key comes from the runner's protected store, never from a committed file
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ERROR: ANTHROPIC_API_KEY is not set in the runner's protected store" >&2
exit 1
fi
export ANTHROPIC_API_KEY
# Authentication precedence: API key above interactive subscription in CI
# No interactive login step exists in this runner; the key is the only credential
# Run with dontAsk: auto-approves reads but still honors deny rules
# A matching deny wins over dontAsk and over bypassPermissions
claude -p \
--output-format json \
--permission-mode dontAsk \
--max-turns 8 \
"Audit this repository for hardcoded secrets and report findings" \
> audit.json
# Prove the deny boundary: this edit would be asked in dontAsk and blocked by a matching deny
# The process exits non-zero rather than promptingWhat this proves: deny rules are evaluated before mode and allow, so a Bash(git push --force*) pattern blocks even when --permission-mode bypassPermissions is set. Advisory controls such as prompt text or CLAUDE.md instructions do not provide that guarantee. Secret posture is that the key is injected from a protected store, validated at start, and never echoed or written to a file that could be committed.
Mechanism reference: 3.6.11 Exit-status semantics for gating
A -p invocation exits non-zero when it errors, for example when a required tool call is denied because nothing in --allowedTools or permissions.allow covers it, or when authentication is missing. The pipeline must check that status and fail the build rather than inferring success from the presence of output text.
The robust gate for schema-validated steps is two-part: first gate on the process exit code, then assert that structured_output is populated and that subtype is not an error variant. A truncated or retry-exhausted run may still exit with output that lacks the validated object, so gating only on the code is necessary but not sufficient.
# Minimal correct gating for any headless step
claude -p "Check for regressions" --output-format json > result.json
status=$?
if [[ $status -ne 0 ]]; then
echo "Claude Code step failed with exit $status" >&2
exit $status
fi
# Stronger gating for schema-validated steps: also assert the contract
jq -e '.structured_output' result.json > /dev/null || {
echo "Contract missing: structured_output not present (subtype: $(jq -r '.subtype // "unknown"' result.json))" >&2
exit 1
}Streaming consumers must also gate only after the final type: "result" line. Parsing partial lines as complete objects produces false failures.
Mechanism reference: 3.6.12 Session identifiers for resumption in multi-step pipelines
Every run produces a session_id inside the JSON envelope. The CLI also exposes continuation flags so a later step can resume context from an earlier one without re-establishing it from scratch:
-cand--continueresume the most recent conversation in the current directory.-rand--resume <id|name>resume a specific session by identifier.
In a multi-step pipeline, session_id serves as the correlator: the first step writes it, the second step resumes that session to keep context, or starts a fresh independent invocation when isolation is desired. The lesson set notes that session_id and continuation are observable in the envelope and in the flags.
The choice between resumption and isolation is itself a tested distinction. Resumption preserves prior reasoning and is cheaper when the next step is a refinement of the same task. Isolation is correct when the next step must be an independent reviewer that evaluates the artifact without the prior justification bias, see the next subsection.
Mechanism reference: 3.6.13 Independent review invocation separate from the authoring session
This is the fifth required example. It shows two distinct invocations: an authoring session that generates or modifies code and an independent review invocation that has no access to the author's reasoning trace.
#!/usr/bin/env bash
set -euo pipefail
# Example 5: independent review invocation separate from the authoring session
# Session A generates; session B reviews. No shared transcript or notes.
DIFF_FILE="pr.diff"
git diff origin/main...HEAD > "$DIFF_FILE"
# Step 1: generate in session A (writes are allowed here because generation is the goal)
claude -p \
--output-format json \
--permission-mode acceptEdits \
--allowedTools "Read,Write,Edit,Bash" \
--max-turns 20 \
"Implement the authentication middleware described in the task at docs/task.md. \
Follow the project's CLAUDE.md conventions and run lint after edits." \
> gen-result.json
GEN_SESSION=$(jq -r '.session_id // "unknown"' gen-result.json)
echo "Generation session: $GEN_SESSION"
# Step 2: review in session B - independent, no transcript from session A
# Only the code, the diff, and the standard are supplied. The reviewer's
# reasoning starts fresh so it is free to flag what the author justified.
REVIEW_SCHEMA='{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"line":{"type":"integer"},"severity":{"type":"string","enum":["low","medium","high","critical"]},"message":{"type":"string"}}}}},"required":["findings"],"additionalProperties":false}'
cat "$DIFF_FILE" | claude -p \
--output-format json \
--json-schema "$REVIEW_SCHEMA" \
--permission-mode dontAsk \
--tools "Read,Grep,Glob" \
--max-turns 12 \
"Review this diff for security issues, error handling gaps, and edge cases. \
Do not modify any file. Return findings with file, line, severity, message." \
> review-result.json
# Review gate: the reviewer is read-only, so its exit is purely about analysis
jq -e '.structured_output' review-result.json > /dev/null || {
echo "Review step produced no structured_output" >&2
exit 1
}
REVIEW_COUNT=$(jq '.structured_output.findings | length' review-result.json)
echo "Independent review produced $REVIEW_COUNT findings (session $(jq -r '.session_id' review-result.json))"
# Post findings downstream from the independent session, not from the generation session
jq '.structured_output.findings' review-result.json > findings.jsonWhat this proves: independent review is achieved by launching a second claude -p process with no --continue or --resume pointing at the generation session. The reviewer receives only the diff and the standard; passing the generation session's transcript or full notes would reintroduce the bias. The acceptEdits mode is appropriate for the generation step, dontAsk plus --tools "Read,Grep,Glob" is appropriate for the read-only review step. The observable difference is that the reviewer can flag a dropped NOT NULL constraint or a weak error handler that the author already argued away.
Mechanism reference: 3.6.14 Incremental review and duplication suppression
Automated review runs on every push. Without memory of prior runs each execution scans the entire pull request from scratch and re-emits every finding, so fixed issues quietly vanish but issues the developer saw and deliberately left alone reappear on every push and erode trust.
The deterministic fix is to carry prior findings into the next invocation and instruct the model to report only new or still-unaddressed items. The prompt supplies the previous structured_output and adds an explicit instruction to suppress already-addressed findings:
PREVIOUS_FINDINGS=$(cat findings-last-run.json 2>/dev/null || echo "[]")
claude -p --output-format json \
--json-schema "$REVIEW_SCHEMA" \
"Review this PR. Here are the findings from the previous review:
$PREVIOUS_FINDINGS
Report ONLY:
1. New issues not in the previous findings
2. Issues from the previous findings that are still present
Do NOT re-report previous findings the developer has already reviewed and chosen not to act on."The stored artifact for incremental context can be the previous structured_output file, a JSON artifact attached to the workflow run, or a committed findings file. The pipeline must handle the first-run case where no previous artifact exists by running a full scan.
A related but distinct optimization is scoping each run to only changed files to reduce context-window pressure. That optimization reduces token usage, not duplicate comments, and does not replace incremental context.
Mechanism reference: 3.6.15 System prompt flags and the append versus replace trap
The CLI provides four system-prompt flags that are directly tested on their append versus replace semantics:
--system-prompt "<text>"replaces the entire default system prompt.--system-prompt-file <path>replaces the default prompt with the contents of a file.--append-system-prompt "<text>"appends text to the default prompt.--append-system-prompt-file <path>appends a file's contents to the default prompt.
Appending keeps default tool guidance, safety instructions, and coding conventions; only the extra rules need to be supplied. Replacing drops everything, so the caller owns the full identity and permission model for that invocation, which is appropriate for non-coding agents in unattended pipelines where the default coding assistant identity is wrong. A distractor is any answer that appends when replacement is needed or replaces when only an addition was wanted.
All four flags apply only to the current invocation and do not modify CLAUDE.md or settings.json on disk, the same per-invocation property that permission and model flags have.
Mechanism reference: 3.6.16 CLAUDE.md as the CI context channel
Every headless run loads CLAUDE.md files by default, concatenated from global through project and local layers, plus lazily for subdirectories Claude actually touches. In CI that means a checkout that includes a well-written project CLAUDE.md already supplies testing standards, fixture locations, coverage targets, and review criteria without any extra pipeline configuration.
What to place in the CI-relevant section:
- Testing standards such as factory pattern location, integration database setup, and coverage target.
- Available fixtures with exact paths.
- Review severity definitions so the model distinguishes critical findings from style nits.
- Existing coverage notes so generated tests target gaps rather than duplicates.
Without this context, CI-invoked test generation suggests tests that already exist and reviews apply generic thresholds. CLAUDE.md alone does not fix duplication on its own; including existing tests in the prompt is still needed when the task is explicitly to avoid duplicate scenarios.
Mechanism reference: 3.6.17 Providing existing tests to avoid duplication
When the pipeline task is to generate tests, the prompt should include the existing test files for the area under change. The agent can then identify coverage gaps instead of re-deriving tests that are already present. The observable improvement is that the second generation run with existing tests in context produces fewer duplicate suggestions and higher new-coverage yield than the same prompt without them.
Mechanism reference: 3.6.18 Batch API versus real-time for CI workflow placement
The Message Batches API is a cost-optimized async bulk path. Once created a batch shows processing_status in_progress until ended, each request expires if not completed within 24 hours (expires_at = created_at + 24h), and results are fetched from results_url as newline-delimited JSON matched on custom_id where order is not guaranteed. Typical batches finish within about an hour but the service-level allowance is up to 24 hours, and polling uses GET /v1/messages/batches/{id}.
That trade cannot serve a blocking gate. A pre-merge check is a blocking workflow: a developer cannot merge until it completes, and latency of hours is unacceptable. Batches is suitable for nightly evaluations of thousands of prompts, weekly audits, or technical-debt reports that are reviewed the next morning.
# WRONG for a blocking gate: async batch with 24h expiry, no latency SLA
# Do not gate a deploy on a batch results_url; the job may still be in_progress
# RIGHT for a blocking gate: synchronous print mode with deterministic contract
git diff origin/main...HEAD | claude --bare -p \
--output-format json \
--json-schema '{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail"]},"reasons":{"type":"array","items":{"type":"string"}}},"required":["verdict","reasons"]}' \
--permission-mode dontAsk \
| jq -e '.structured_output.verdict == "pass"' || exit 1Mechanism reference: 3.6.19 Permissions, trust, and the governance stack beyond flags
Pipeline permission design participates in a wider governance stack that the lessons call out explicitly: permissions, observability, evaluation, and policy. Flags are the permissions layer. Hooks and CLAUDE.md conventions are the policy layer. Cost and session fields plus PostToolUse hooks form the observability layer. The recommendation is to start strict and relax as trust is established, with audit trails for every step.
Trust calibration in the governance lesson ranks task risk from fully trusted reversible reads at the low end to zero-trust irreversible deploys at the high end. In pipeline terms this means plan or dontAsk plus read-only tools for review steps, acceptEdits for bounded fix steps inside sandboxed runners, and deny rules plus human approval for push or publish steps.
Mechanism reference: 3.6.20 Hooks, deny-before-allow evaluation, and session-isolation primitives
Permission evaluation is not a single flag check. The CLI applies a layered order that the lesson set places roughly as hooks first, then deny rules, then ask or defer, then the active mode, then allow. A PreToolUse hook runs before deny, mode, and the programmatic canUseTool callback in the Agent SDK embedding, and a deny rule wins even when the mode is bypassPermissions. This is why permissions.deny entries for Bash(rm -rf ) or Bash(git push --force) survive an otherwise permissive autonomous run.
Subagents inherit the parent's bypassPermissions mode and cannot be separately constrained to a narrower mode once the parent is permissive. For that reason the secure pattern is to avoid permissive modes entirely in multi-step pipelines and to scope each step to the narrowest mode it actually needs.
The forensic file also flags a claim about a read-only advisory flag. No standalone --read-only is documented as a distinct CLI flag in the current reference; the enforced read-only control is --permission-mode plan. Advisory signals such as prompt instructions or CLAUDE.md rules can be drifted past by the model under tool pressure or injected content, while tool restrictions and deny rules are enforced by the client regardless of model output.
Ownership map
Which layer owns which guarantee when Claude Code runs in a pipeline. The map makes explicit who is responsible for what, so a failure can be traced to the right layer.
| Guarantee | Owner | How it is observed | Failure signal |
|---|---|---|---|
| Terminating headless execution | CLI layer (-p and --print) | Process writes to stdout and exits; runner sees a finite job | Hang until runner timeout, no stdout |
| Machine-parseable shape | CLI layer (--output-format json and stream-json) | Single JSON envelope or line-delimited JSON stream | Prose scraping breaks on wording drift |
| Typed payload with exact fields | Application contract via CLI (--json-schema plus structured_output) | structured_output present with required fields and valid types | Missing structured_output or subtype error variant |
| Bounded cost and latency | Application layer (--max-turns) plus runner timeout | Turn count capped; job finishes within budget | Open-ended loop, cost spike, wall time overrun |
| Permission policy per run | CLI plus configuration layer (--permission-mode, --allowedTools, --disallowedTools, --tools, settings.json permissions) | Tool calls succeed or are denied without prompting; deny wins over mode | Prompted or hung on approval, or disallowed tool executed |
| Session correlator and resumption | CLI layer (session_id plus -c and --continue, -r and --resume) | Second step resumes prior session or starts isolated one as intended | Lost context or unintended bias from shared transcript |
| Project context for CI | Configuration layer (CLAUDE.md hierarchy, CLAUDE.local.md excluded) | CI run follows team testing and review standards | Generic boilerplate, duplicated tests, wrong severity |
| Deterministic versus probabilistic control | Architecture split: enforced controls live in CLI and config, advisory controls live in model prompt | Enforced deny or tool removal blocks regardless of wording; prompt-only instruction may drift | Model acts despite instruction, especially under injected tool output |
| Secret presence for API credential | Infrastructure layer (ANTHROPIC_API_KEY in protected store) plus CLI error path | Run authenticates immediately; missing key fails fast with non-zero exit | Hang or opaque auth error mid-run |
| Streaming delivery without loss | CLI layer (--output-format stream-json plus --verbose and --include-partial-messages) | Consumer reassembles text_delta events and gates only on final type: "result" | Partial-line parse as complete JSON, lost deltas, early gate |
The SDK embedding adds two API-level guarantees that also belong on the map: the canUseTool callback for per-call programmatic approval and the permissionDecision value ask that surfaces a human prompt from a hook, plus max_budget_usd for dollar-capped sessions.
Version and terminology currency
The reference page and the lesson set already use current terminology, but four currency notes matter when reading older community material.
- Headless invocation is
-pand--print. Older posts may refer to--headless,-y,--yes, or environment tricks as if they were the switch. They are not. The only documented values are-pand--print. - Structured output is
--output-format jsonwith an optional--json-schemafor a typed contract. Older material that says--jsonor--output-fileis using a non-existent spelling. The schema requires JSON Schema draft-07 and requires--output-format jsonin the same command. - Read-only control is
--permission-mode plan. References to a standalone--read-onlyflag should be read as theplanmode unless the current CLI reference is re-checked and shows otherwise. Treat--read-onlyas unverified against the present docs. - Autonomous permission bypass is
--dangerously-skip-permissionsas a flag and--permission-mode bypassPermissionsas the mode value. Older material may call this--yesor--no-confirm. Those are distractors, not synonyms. CLAUDE.mdloading in headless runs applies by default and is skipped only with--bare. The additional-directories gateCLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1for--add-dirmemory loading is the current spelling.- Batch versus synchronous wording shifted to the Message Batches API as the async bulk path. Exam material may describe batch processing as just a flag rather than the API's
processing_status,expires_at, andresults_urlcontract. The latter is the documented shape.
The lesson set also notes version floors for newer flags such as --json-schema at v2.1.205 and a 10 MB stdin cap at v2.1.128, which explains why some older runners show different limits.
Official versus community divergence
Where community material contradicts documentation, the documentation position is the one to answer with. Three divergences are common in this task.
- Non-interactive switch. Community posts propose
CLAUDE_HEADLESS=true,--batch,< /dev/null, or--non-interactive. Documentation defines-pand--printas the only switch. Reason the community drifts: headless naming from browsers and test frameworks transfers by analogy, and stdin redirection is a real Unix trick that happens to not engage the CLI's mode flag. - Machine-parseable output. Community posts suggest capturing prose and regexing fields, or using
--jsonas a short flag, or placing the schema in the prompt. Documentation defines--output-format jsonplus--json-schemawith a draft-07 schema and a dedicatedstructured_outputenvelope field. Reason the community drifts: short flags feel natural and prompt-only JSON often works in demos, masking the need for an enforced contract in a blocking gate. - Enforcement versus advisory. Community snippets rely on
CLAUDE.mdinstructions such as never run a command or on post-action audit hooks to prevent edits. Documentation treatssettings.jsondeny rules,--disallowedTools, and--toolsrestriction as the hard boundaries, withPreToolUsehooks for determinism and instruction text as defense in depth. Reason the community drifts: prose instructions are easier to add than a settings change, and they appear to work until the model is pressured by long context or adversarial tool output.
The correct exam choice in each divergence is the documented flag or evaluation rule, even when the community alternative looks simpler. A question that shows a hanging job and offers both -p and CLAUDE_HEADLESS=true always takes -p; a question that shows a prompt-only JSON instruction and a schema flag always takes the schema flag for a blocking gate; a question that shows a CLAUDE.md instruction versus a deny rule for a destructive command always takes the deny rule.
Beyond the task statement
The lesson set covers adjacent topics the reference page omits entirely. Each entry below names the topic, why it matters for this task, and the lesson slug where it is taught.
CLAUDE.mdhierarchy and concatenation. CI runs load global, project, local, and lazily directory-level files as concatenated layers, not overrides. Understanding this explains why a shared projectCLAUDE.mdalready shapes CI output without extra workflow configuration.@pathimport composition with four-hop recursion and code-span safety. Imported conventions compose across repos and monorepo packages, which matters when a pipeline reuses shared standards.settings.jsonprecedence from user through project and local to--settingsto managed, with managed always winning, scalars overriding and arrays concatenating. Pipeline isolation depends on this order.$schemahinthttps://json.schemastore.org/claude-code-settings.jsonfor editor validation of settings files. A misspelled deny rule is otherwise silent.- Enterprise keys
companyAnnouncements,disableBundledSkills,skillOverrides, andavailableModels. These gate what a project can set and what skills are active, which affects which tools a pipeline step can reach. - Full hook surface of roughly thirty events across session, prompt, tool, subagent and task, workspace, and lifecycle families, with five handler types
command,http,mcp_tool,prompt, andagent, and a structuredhookSpecificOutputdecision that merges by deny greater than defer greater than ask greater than allow. claudeMdExcludesfor tidy discovery and--add-dirgateCLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD. Extra directories are reachable to tools but do not load extraCLAUDE.mdwithout the opt-in variable.- SDK selectors
settingSourcesandsystemPrompt.preset. An SDK-hosted pipeline that ignoresCLAUDE.mdis usually missing asetting_sourcesentry, not a broken file path. - Agent SDK programmatic controls
canUseTool,permissionDecisionvalues includingaskfor human-in-the-loop,permission_modebaselines,max_budget_usdand theerror_max_budget_usdsubtype, and theCLAUDE_CODE_USE_BEDROCKandCLAUDE_CODE_USE_VERTEXrouting variables. - Trust calibration and the governance stack permissions, observability, evaluation, and policy, including human-in-the-loop versus human-on-the-loop versus autonomous modes and escalation mechanisms such as retry limits and cost limits.
/hooksinspection plusPreToolUse,PostToolUse,Notification, andStopevent families for enforcement versus detection versus notification. CI hardening uses the first two.
Worked production examples
Two end-to-end walkthroughs with concrete values, reasoning chains, and the failure mode each avoids.
Worked production examples: Example A: Blocking security gate that cannot be bypassed by wording drift
Context. A team merges through a pipeline workflow file that must block on security findings. The gate must be machine-parseable, must not depend on the model following a prompt-only JSON instruction, and must fail the build on any contract violation. Human reviewers are not in the loop for this step, so permission handling must be explicit and verifiable.
Plan. Use a headless invocation with -p, --output-format json, and --json-schema carrying a draft-07 contract. Run under --permission-mode dontAsk with a deny rule blocking force pushes and package publishes. Gate on the process exit code and on structured_output presence and verdict.
Execution. The workflow file checks out the repository, generates a diff, and runs a single deterministic step:
#!/usr/bin/env bash
set -euo pipefail
# Blocking gate - concrete values, real failure handling
SCHEMA='{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail"]},"reasons":{"type":"array","items":{"type":"string"}},"findings":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"line":{"type":"integer"},"severity":{"type":"string","enum":["low","medium","high","critical"]},"message":{"type":"string"}},"required":["file","line","severity","message"]}}},"required":["verdict","reasons","findings"]}'
git diff origin/main...HEAD | claude --bare -p \
--output-format json \
--json-schema "$SCHEMA" \
--permission-mode dontAsk \
--max-turns 10 \
--append-system-prompt "You are a security reviewer for this codebase. Follow CLAUDE.md severity definitions." \
"Review this diff for security issues. Return verdict, reasons, and findings" \
> gate.json
# Gate 1: process exit code already enforced by set -e
# Gate 2: structured_output must exist
jq -e '.structured_output' gate.json > /dev/null || {
echo "Gate failed: structured_output missing, subtype $(jq -r '.subtype // "unknown"' gate.json)" >&2
cat gate.json >&2
exit 1
}
# Gate 3: verdict must be pass
if ! jq -e '.structured_output.verdict == "pass"' gate.json > /dev/null; then
echo "Gate failed: verdict $(jq -r '.structured_output.verdict' gate.json) reasons $(jq -c '.structured_output.reasons' gate.json)" >&2
# Post findings from the validated object, at exact file and line, not from free text
jq -c '.structured_output.findings[]' gate.json | while read -r f; do
echo "Finding $(echo "$f" | jq -r '"\(.file):\(.line) [\(.severity)] \(.message)"')"
done
exit 1
fi
echo "Gate passed - cost $(jq -r '.total_cost_usd' gate.json) session $(jq -r '.session_id' gate.json)"Reasoning chain. -p guarantees termination without a terminal. --output-format json guarantees an envelope with result, structured_output, session_id, and total_cost_usd. --json-schema guarantees the typed verdict and findings fields are present and typed, and the re-prompt on mismatch avoids a parser break. dontAsk guarantees no prompt hang, while the deny rule guarantees a destructive command cannot slip through even if the model is instructed to run it. The CLAUDE.md memory load guarantees severity labels match team definitions without restating them in the workflow file.
Failure mode avoided. Two common variants fail this gate. A prompt-only JSON instruction without --json-schema emits text that occasionally drifts shape and breaks the jq consumer. A run that gates only on the process exit code lets a retry-exhausted schema violation through as a success because exit alone was zero while structured_output was missing. Both are caught by the three-part gate.
Observable output. On pass: a log line with cost and session plus an artifact gate.json with a populated structured_output.verdict of pass. On fail: a non-zero workflow step with a log that includes the verdict, reasons, and each finding printed as file:line [severity] message, exactly the fields downstream tooling needs to post inline comments.
Worked production examples: Example B: Generation and independent review with incremental suppression across pushes
Context. A repository merges a large feature over several pushes. One pipeline step generates implementation code; a second step reviews it. After the first review the developer pushes a small fix. The pipeline must not re-emit the same findings the developer already saw and chose to leave, or trust collapses and findings are ignored.
Plan. Generate in session A with acceptEdits and a bounded turn count. Review in session B as an independent claude -p invocation with dontAsk plus --tools "Read,Grep,Glob" so it can only report. Store session B's structured_output as findings-last-run.json. On the next push, inject that file into the new review prompt with an explicit suppression instruction.
Execution across two pushes.
Push 1, generation:
claude -p --output-format json \
--permission-mode acceptEdits \
--allowedTools "Read,Write,Edit,Bash" \
--max-turns 20 \
"Implement the rate limiter described in docs/rate-limiter.md per CLAUDE.md. Run lint and typecheck." \
> gen.jsonPush 1, independent review:
REVIEW_SCHEMA='{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"line":{"type":"integer"},"severity":{"type":"string"},"message":{"type":"string"}}}}},"required":["findings"]}'
git diff origin/main...HEAD > pr.diff
cat pr.diff | claude -p \
--output-format json \
--json-schema "$REVIEW_SCHEMA" \
--permission-mode dontAsk \
--tools "Read,Grep,Glob" \
--max-turns 12 \
"Review this diff for correctness and error handling. Return findings with file, line, severity, message." \
> review1.json
jq '.structured_output' review1.json > findings-last-run.jsonPush 2, incremental review:
PREV="$(cat findings-last-run.json)"
cat pr.diff | claude -p \
--output-format json \
--json-schema "$REVIEW_SCHEMA" \
--permission-mode dontAsk \
--tools "Read,Grep,Glob" \
--max-turns 12 \
"Review this PR. Previous findings: $PREV
Report ONLY:
1. New issues not in the previous findings
2. Issues from the previous findings that are still present
Do NOT re-report previous findings the developer has already reviewed and chosen not to act on." \
> review2.json
# Only new or still-present findings are posted
jq -c '.structured_output.findings[]' review2.json | while read -r f; do
file=$(echo "$f" | jq -r '.file')
line=$(echo "$f" | jq -r '.line')
echo "Posting incremental comment on $file:$line"
doneReasoning chain. Two claude -p invocations with no --continue or --resume between them guarantees isolation: the reviewer starts from the diff and the standard only, so it is free to question a trade the author already defended. dontAsk plus --tools restriction guarantees the reviewer cannot modify code even when asked to fix a finding. Injecting findings-last-run.json and asking for only new or still-present items guarantees the second push does not flood the pull request with duplicates, preserving signal. Handling the missing-file case on the first run guarantees the incremental instruction is skipped when no history exists.
Failure mode avoided. Self-review inside the authoring session reproduces the author's justifications and misses a dropped constraint in a migration that an independent reviewer would catch. A stateless per-push review without incremental context reposts five findings on every push while the developer fixes none, so the sixth push's real new finding is lost in noise. Both patterns are avoided by isolation plus incremental context.
Observable output. After push 1: findings-last-run.json with an array of findings carrying file, line, severity, and message. After push 2: review2.json whose structured_output.findings is a strict subset containing only new or still-present items, and a workflow run where only that subset is posted to the pull request.
Build exercise material
These exercises are verifiable step by step. Each step states what to run and the observable outcome that proves it worked. Every step uses the flags and files the lesson set documents.
Build exercise material: Exercise 1: Make a hanging job terminate
Goal: prove that -p is the switch that fixes a hang.
- Step 1: create a minimal repository with a checked-in
CLAUDE.mdthat declares testing standards and review criteria. - Step 2: write a shell script that runs
claude "Review this diff"without-pbehind a 30 second timeout. Observable: the job times out with no stdout and a non-zero timeout exit, or hangs visibly longer than the same prompt with-p. - Step 3: add
-pto the same command and run again. Observable: the process writes to stdout and exits with status zero within a few seconds, producing aresultfield when--output-format jsonis also added. - Step 4: add
--output-format jsonand parse withjq -r '.result'. Observable:jqextracts the result without error andjq -r '.session_id'returns a non-empty identifier. - Step 5: attempt the distractor
CLAUDE_HEADLESS=true claude "Review this diff"and confirm it still hangs or is not faster than the-pvariant. Observable: no improvement; the documented flag is the only fix.
Build exercise material: Exercise 2: Ship a blocking gate with a typed contract
Goal: enforce a deterministic blocking gate with structured_output.
- Step 1: write
verdict.schema.jsonmatching the draft-07 schema used earlier, requiringverdict,reasons, andfindingswithfile,line,severity, andmessage. - Step 2: run
claude --bare -p --output-format json --json-schema "$(cat verdict.schema.json)" "Return a pass fail verdict"and write togate.json. - Step 3: run
jq -e '.structured_output' gate.json > /dev/null || exit 1. Observable: the assertion passes when the schema is satisfied and fails closed when it is not. - Step 4: break the schema deliberately by asking the model to return a string instead of the required object, and confirm the gate fails closed rather than passing a malformed object downstream.
- Step 5: verify that
jq -e '.structured_output.verdict == "pass"' gate.jsongates the pipeline correctly and thattotal_cost_usdandsession_idare present for logging.
Build exercise material: Exercise 3: Prove plan is the read-only boundary
Goal: resolve the forensics question on a read-only flag versus the plan permission mode.
- Step 1: run a read-only audit with
claude -p --permission-mode plan --output-format json --tools "Read,Grep,Glob"and ask the model to edit a file. Observable: the run completes without writing the file, and any tool attempt to write is denied before execution. - Step 2: run the same prompt without
planor tool restriction and with an advisory--append-system-prompt "Do not modify any file". Observable: the model may still attempt a write under tool pressure, proving advisory text is not a boundary. - Step 3: verify that a standalone
--read-onlyflag is not listed inclaude --helpor the CLI reference. Observable: help output does not show it as a current flag, confirmingplanis the documented read-only control.
Build exercise material: Exercise 4: Wire CLAUDE.md so CI tests follow team patterns
Goal: prove that CLAUDE.md shapes CI output without extra workflow flags.
- Step 1: add a CI-relevant section to
.claude/CLAUDE.mdlisting the factory pattern path, fixture paths, integration setup, and the coverage target. - Step 2: run
claude -p --output-format json "Generate a test for src/auth/middleware.ts"without any prompt-side convention restatement. Observable: the generated test imports from the documented factory and fixture paths and follows the coverage target. - Step 3: repeat the same prompt with existing tests included in context. Observable: the new generation avoids duplicating existing scenarios and targets the gaps.
- Step 4: add
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1withclaude --add-dir ../shared-liband confirm shared library conventions are now applied; without the variable they are not.
Build exercise material: Exercise 5: Isolate a reviewer and suppress duplicates across pushes
Goal: reproduce the independent-review and incremental-context pattern end to end.
- Step 1: run a generation step with
--permission-mode acceptEditsand storegen-result.jsonplussession_id. - Step 2: run an independent review step with
cat pr.diff | claude -p --output-format json --json-schema <schema> --permission-mode dontAsk --tools "Read,Grep,Glob"with no--continueor--resumeto the generation session. Observable: two distinctsession_idvalues ingen-result.jsonandreview-result.json. - Step 3: store
review-result.json'sstructured_outputasfindings-last-run.jsonand simulate a second push that injects it with the suppression instruction. Observable: the second review'sstructured_output.findingscontains only new or still-present items. - Step 4: confirm the posting loop reads from
structured_outputwithjq -c '.structured_output.findings[]'rather than fromresultor from a top-level key.
Build exercise material: Exercise 6: Gate correctly on exit status and on subtype
Goal: harden the workflow file gate beyond a bare exit code check.
- Step 1: write the workflow file pattern shown earlier with
set -euo pipefailand three gates: exit status via the shell,jq -e '.structured_output', andjq -e '.structured_output.verdict == "pass"'. - Step 2: induce a denied tool by running
claude -p --output-format json --permission-mode dontAsk "Edit a file"with no matchingpermissions.allow. Observable: the process exits non-zero and the workflow step fails, rather than producing a success from partial text output. - Step 3: induce a schema-retry exhaustion by requiring an improbable schema shape and confirm
subtypesignals the error variant with a missingstructured_output, caught by the second gate. - Step 4: enable
--output-format stream-json --verbose --include-partial-messageson a long run, consume as line-delimited JSON, and gate only on the finaltype: "result"line. Observable: partial parses without treating intermediate deltas as complete objects.
Mechanism and API surface
- -p print mode gate
- Switches Claude Code to non interactive where prompt is processed, result is emitted to stdout, and the process exits, no keyboard required, hanging CI jobs are always the missing -p.
- --output-format json envelope
- Wraps run with result text, structured_output validated object when schema supplied, total_cost_usd client estimate with per model breakdown, session_id, plus stream-json NDJSON variant for live progress with --verbose and --include-partial-messages.
- --json-schema draft-07 validation
- Requires --output-format json and v2.1.205 plus, validates final output against draft-07 before exit with internal re prompt on mismatch, deterministic enforcement versus probabilistic prompt only instructions.
- System prompt append versus replace
- --append-system-prompt keeps default Claude Code guidance plus extra rules, --system-prompt replaces and drops safety guidance, append is the safe default for headless coding, replace only for non coding identities.
- --permission-mode six values
- default standard with abort on unapproved in -p, acceptEdits auto accept edits in working directory, plan read only, dontAsk auto deny not allow listed, bypassPermissions skip prompts in trusted sandboxes with org disable flag, plus --max-turns cost bound and --bare minimal skips all discovery.
- Session isolation and incremental context
- Independent claude -p invocations for generate versus review to remove generation reasoning, plus prior findings re included as context with report only new or still unaddressed instruction to avoid duplicate comments across pushes.
- Batch API matching rule
- 50 percent savings but up to 24 hour no SLA processing and no multi turn tool calling per batch request, synchronous for blocking pre merge checks, batch for overnight latency tolerant reports with custom_id correlation.
The decision rules in play
Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.
The `-p` / `--print` flag is the mandatory non-interactive entry point
The CLI defaults to an interactive REPL. When invoked with no mode flag, it allocates a conversational session that expects a human to type, to approve actions, and to answer follow-up prompts. A headless pipeline has no terminal and no human, so the process blocks on a read that never arrives and the runner eventually kills it at the job timeout. Passing -p (long form --print) switches the process into single-shot mode: it reads the prompt argument, performs the work, writes the result to standard output, and exits. The pipeline then sees a normal terminating process with a checkable exit status.
# Hangs in CI: no human to satisfy the interactive loop
claude "Review this pull request for security issues"
# Correct: non-interactive single-shot run
claude -p "Review this pull request for security issues"The interactive loop exists because the tool was built for a developer at a keyboard. The non-interactive path is a distinct execution mode that the authors wired to a specific flag. There is no ambient global switch that infers "no terminal present, therefore batch mode"; the tool must be told explicitly. That is why the fix is always the flag, not an environment trick or a redirection.
Boundary. If a human is genuinely at a terminal, omitting -p is correct and desirable: the developer wants the REPL. The opposite answer becomes right precisely when interactivity is available and wanted. In any unattended context, -p is mandatory regardless of how simple the prompt looks.
Recurring specifics. The flag appears as -p and as --print interchangeably in the tested material. The prompt is supplied as a positional argument. The output goes to stdout. The run exits with a status the pipeline inspects. A job that "hangs indefinitely, then times out with no output" is the canonical signal that -p is missing.
Proposal. set CLAUDE_HEADLESS=true.
Why it attracts. "headless" is a familiar term from browser automation.
Why it fails. no such variable exists in the CLI.
When it would be right. never, for this tool.
Proposal. add --batch.
Why it attracts. batch processing is a common name for non-interactive work.
Why it fails. the flag does not exist.
When it would be right. never.
Proposal. redirect stdin from /dev/null.
Why it attracts. it is a real Unix technique that stops some programs from blocking.
Why it fails. it treats a CLI mode problem as a stream problem and the process can still wait.
When it would be right. only as a belt-and-suspenders guard, never as the primary fix.
- Some items add
--output-format jsonto the hanging command and ask whether that alone fixes it. It does not: structured output controls parsing, not interactivity. Others ask whether a prompt instruction telling the agent to avoid follow-ups fixes it. It does not, because the CLI itself waits, not the model.
`CLAUDE_HEADLESS=true` is a fabricated environment variable (false cognate)
A recurring wrong answer instructs the engineer to export an environment variable named CLAUDE_HEADLESS and set it to true before invoking the CLI, implying the variable flips the tool into non-interactive mode. No such variable is documented. The tool has no "headless" environment toggle; the only legitimate non-interactive switch is the -p flag.
The name is a deliberate false cognate. "Headless" is the standard label for non-interactive execution in browser-automation and test frameworks, so a candidate who knows those ecosystems reaches for it by pattern matching. The exam exploits that transfer: the plausible-sounding variable is exactly the kind of thing a confident engineer would try first and the kind of thing that is absent from the real CLI surface.
Boundary. For tools that do define a headless variable, the pattern works. The opposite answer is correct there. For this CLI it is never correct, because the mode is flag-gated, not environment-gated.
Recurring specifics. The variable name CLAUDE_HEADLESS appears with the value true. It is paired in distractors with --batch and with stdin redirection, forming a trio of "non-existent or wrong-layer" fixes. The correct answer is always the -p flag instead.
Proposal. the variable exists and is the approved switch.
Why it attracts. familiar naming.
Why it fails. not in the documented flag or environment set.
When it would be right. in a different product that documents such a variable.
- Sometimes the variable is presented as
CLAUDE_MODE=CIorCLAUDE_OUTPUT=json. Both are equally fabricated and serve the same distractor purpose: a config-looking knob that does not exist.
`--batch` is a fabricated flag (false cognate)
Another frequent wrong answer adds a --batch flag to force non-interactive processing. The flag does not exist. "Batch mode" is a natural name for running many items without a prompt, so it reads as correct to anyone who has used other CLIs, but the CLI has no such option.
The exam reuses the same cognitive trap as the headless variable: a reasonable, industry-standard name for a feature the tool genuinely needs, attached to a flag that was never shipped. Candidates who reason by analogy rather than from the documented flag set choose it.
Boundary. Where a tool ships a --batch flag, it is the right call. Here the equivalent behavior is expressed by -p plus optional looping in the shell, not by a single mode flag.
Recurring specifics. --batch is offered alongside --headless and --non-interactive as interchangeable "enable automation" flags. All three are wrong. The presence of --batch in an option list is a reliable signal that the correct choice is the -p entry.
Proposal. --batch forces non-interactive execution.
Why it attracts. standard batch nomenclature.
Why it fails. absent from the CLI.
When it would be right. in a tool that documents it.
- In some items
--batchis combined with--format=jsonto look like a complete automation invocation. The combination is doubly wrong: neither flag is real.
stdin redirection from `/dev/null` is an anti-pattern, not the fix
A wrong answer redirects standard input from the empty device so the process cannot block waiting for keystrokes: claude "..." < /dev/null. This is a legitimate Unix technique that stops some interactive programs from hanging, but it does not properly engage the CLI's non-interactive mode and the process can still wait on other interactive conditions.
The redirect attacks the symptom (a blocked read) at the stream layer rather than the cause (the REPL loop). Even with an empty stdin, the tool may still expect interaction or behave unpredictably. The documented flag is the clean, intended control.
Boundary. For programs whose only interactive dependency is a blocking stdin read, the redirect can suffice. The opposite answer is right there. For this CLI, -p is the correct and sufficient control and the redirect is at best a secondary guard.
Recurring specifics. The pattern < /dev/null appears as a standalone fix and as part of a multi-option list where -p is the correct sibling. The teaching point is consistent: use the documented mode flag, not a stream workaround.
Proposal. redirect stdin and the hang is solved.
Why it attracts. it is a real, working trick for many tools.
Why it fails. it does not set non-interactive mode.
When it would be right. for stdin-only interactive tools.
- Some items pair the redirect with a prompt instruction to "avoid follow-up questions." Both fail for the same reason: they try to influence behavior the CLI controls via mode, not via input or wording.
The `--non-interactive`, `--silent`, `--json`, `--quiet`, `--headless`, `--pipeline`, `--yes`, `--no-confirm`, `--review`, `--output-file` family are all nonexistent or wrong-purpose distractors
This rule collects the broader set of invented or misapplied flags that appear across the tested material as attractive but incorrect answers. None of --non-interactive, --silent, --headless, --pipeline, --yes, --no-confirm, --review exist as the claimed control. --json, --quiet, and --output-file are wrong-purpose: they name a real-sounding capability but are not the documented mechanism.
Each name maps to a concept the tool supports, just expressed elsewhere. "Non-interactive" is -p. "Quiet" sounds like suppressed UI but the real flag for clean output is --print. "JSON output" exists but as --output-format json, not --json. "Review mode" sounds plausible but there is no built-in --review subcommand; review is a prompt plus structured output. The exam tests whether the candidate knows the exact spelling and layer.
Boundary. Where a sibling product ships --yes or --non-interactive, those are correct. Here they are traps. The nearby correct form is always the documented flag: -p, --print, --output-format json, --dangerously-skip-permissions.
Recurring specifics. - --json is offered for "all output in JSON." The correct control is --output-format json. - --quiet is offered to "suppress output." It would suppress the result entirely, the opposite of what a pipeline needs; --print keeps the response text while dropping UI chrome. - --non-interactive is offered to "remove prompts but keep formatting." No such flag exists; --print is the mechanism. - --headless is offered as the automation flag. It does not exist. - --yes and --no-confirm are offered to auto-accept permission prompts. Neither exists; the real flag is --dangerously-skip-permissions. - --review is offered as a built-in PR-review mode emitting Markdown. There is no such mode; review is a prompt plus --output-format json. - --output-file is offered to write structured results to a file. The tool has no such flag; structured output is controlled by --output-format.
Proposal. --json gives machine output.
Why it attracts. short and obvious.
Why it fails. wrong flag name; use --output-format json.
When it would be right. in a tool that defines --json.
Proposal. --quiet silences noise.
Why it attracts. standard quiet flag.
Why it fails. it silences the answer too.
When it would be right. when you genuinely want no stdout.
- These flags are rotated through dozens of items so the same concept is tested under many names. The constant is: exact documented spelling wins, plausible synonym loses.
`--output-format json` wraps the run in a machine-parseable envelope
When a headless run must be consumed by another program rather than a human, the --output-format json flag changes what is written to stdout. Instead of bare response text, the CLI emits a JSON envelope that carries the result string alongside metadata such as the session identifier and cost or usage figures. A downstream script reads the envelope and extracts what it needs without scraping prose.
A pipeline has no person reading the terminal, so any consumer of the output is itself software. Free-form text is brittle to parse; an envelope with named fields is stable. The flag exists precisely to make the run a first-class data source for the next step.
Boundary. If a human will read the output, or if the next step is a person copying text, plain --print text is fine. The opposite answer (use JSON) becomes right the moment an automated consumer needs the result. Cost and usage metadata matter for billing dashboards; the text form discards them.
Recurring specifics. The flag takes the value json. The envelope is the wrap; the answer text lives inside it, not at the top level. The --output-format flag is distinct from -p: -p controls interactivity, --output-format json controls shape. Both are commonly used together.
Proposal. --json as a top-level flag.
Why it attracts. shorter.
Why it fails. not the documented name.
When it would be right. in a tool that defines --json.
Proposal. capture prose stdout and regex the fields.
Why it attracts. no extra flags.
Why it fails. brittle against wording drift.
When it would be right. only for throwaway one-off scripts.
- Some items ask whether
--output-format jsonalone fixes a hang. It does not: it is about shape, not interactivity. The hang is fixed by-p; the JSON is an addition once the run is non-interactive.
`--json-schema` enforces schema-validated output that lands in `structured_output`
The --json-schema flag takes a JSON Schema and constrains the agent's final structured result to conform to it. Used with --output-format json, the validated object is exposed in a dedicated structured_output field of the envelope, separate from the free-text result. A schema guarantees the downstream consumer receives the exact fields it expects, such as file, line, severity, and message for a finding.
claude -p \
--output-format json \
--json-schema '{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"line":{"type":"integer"},"severity":{"type":"string"},"message":{"type":"string"}}}}}}' \
"Review this PR for security issues"Without a schema, the model may return JSON that is valid but shaped differently than the consumer expects, breaking the parser. The schema moves the contract from "hope the model cooperates" to "the CLI validates before exit." The validated data is placed in structured_output so the pipeline extracts it deterministically with jq '.structured_output'.
Boundary. If the consumer is tolerant and just needs loosely structured data, --output-format json without a schema is enough. The schema becomes necessary when findings must be posted as inline comments at exact file and line, filtered by severity, or tracked across runs. The nearby opposite is a prompt that merely asks for JSON: that is probabilistic and may drift.
Recurring specifics. The flag is --json-schema with a schema string or file reference. The result lands in structured_output, not at the envelope root. The schema enforces types and required fields. Extraction uses jq '.structured_output'. Schema-validated output is print-mode only.
Proposal. define the schema in the prompt and use --output-file.
Why it attracts. looks complete.
Why it fails. --output-file does not exist, and prompt-only schema is not enforced.
When it would be right. never for this tool.
Proposal. parse stdout manually for JSON.
Why it attracts. no schema knowledge needed.
Why it fails. fragile and unnecessary when a built-in flag exists.
When it would be right. only without the CLI.
- Some items ask whether
--json-schemaalone forces termination. It does not force exit by itself; it governs output shape. Others ask whether the schema lives at the envelope top level. It does not; it isstructured_output. Both are tested as traps.
`--output-format stream-json` versus `json` is streaming delta versus single payload
The --output-format flag accepts text, json, and stream-json. The json value emits one complete envelope after the run finishes: a single payload. The stream-json value emits a sequence of JSON objects as the run progresses, each a delta or event, suitable for a consumer that wants progressive updates rather than a wait.
# Single payload after completion
claude -p "Summarize the diff" --output-format json
# Streaming deltas during the run
claude -p "Summarize the diff" --output-format stream-jsonA long review that writes nothing until the end forces the downstream step to wait with no signal of progress, and risks losing everything if the process dies. Streaming lets a consumer render partial results, show a live view, or checkpoint. The choice is about delivery shape, not about whether the run is non-interactive; both require -p.
Boundary. When the next step only acts after the whole result exists, json is simpler and the parser is trivial. The streaming form becomes right when the consumer is itself live (a dashboard, a tailing log, an interactive viewer) or when partial progress matters. The opposite answer is correct for batch post-processing.
Recurring specifics. The three values are text, json, stream-json. Streaming is a sequence of objects, not one object. A single-payload consumer must not try to parse stream-json as one JSON document. The flag is --output-format, not --stream.
Proposal. --stream as a standalone flag.
Why it attracts. short.
Why it fails. not the documented name; it is a value of --output-format.
When it would be right. in a tool that defines --stream.
Proposal. treat stream-json as one big JSON.
Why it attracts. looks like JSON.
Why it fails. it is a stream of objects.
When it would be right. never.
- Items sometimes conflate streaming with interactivity: streaming still needs
-p. Others ask whether streaming removes the need for a schema. It does not;--json-schemaapplies to print mode regardless of streaming.
`--input-format text|stream-json` controls how a headless run consumes its prompt
The --input-format flag mirrors the output flag: it tells the CLI how to read the prompt it is given. The default is text, where the prompt is a single string argument. The stream-json value lets the caller feed a stream of JSON input events, useful when the driving program already produces structured deltas rather than one blob.
A pipeline that generates its prompt incrementally (for example, by walking a file list and emitting one record per file) can stream that prompt in rather than buffering the whole thing. The flag keeps the contract symmetric with --output-format stream-json.
Boundary. For a static prompt string, text is correct and input-format need not be set. The streaming form becomes right when the producer is itself streaming. The opposite answer is correct for one-shot prompts.
Recurring specifics. The accepted values are text and stream-json. It pairs conceptually with --output-format stream-json but is independent: you can stream input and take a single payload output, or vice versa.
Proposal. pipe the prompt via stdin and call it stream mode.
Why it attracts. piping feels streaming.
Why it fails. streaming is selected by --input-format stream-json, not by a pipe.
When it would be right. never without the flag.
- Items may ask whether
--input-formatfixes a hang. It does not; interactivity is still governed by-p.
Exit codes gate the pipeline: zero success, non-zero failure, checked after the run
The CLI follows standard Unix convention: it exits 0 on success and with a non-zero status on error. A pipeline step should check that status to decide whether the job passes or fails. This is the most reliable signal of step health because it does not depend on parsing prose that may change wording between runs.
claude -p "Generate the API docs" --output-format json
status=$?
if [ "$status" -ne 0 ]; then
echo "Claude Code step failed with exit code $status" >&2
exit 1
fiA pipeline is a sequence of processes; the runner keys off exit codes to halt and mark failure. If the Claude step errors but exits 0, the runner proceeds on broken data. Inspecting the code makes the gate deterministic regardless of output formatting.
Boundary. If the step is best-effort and the pipeline should continue regardless, the code may be ignored. The opposite answer (gate on code) is correct for any step whose result the next stage depends on. Fragile output parsing as a proxy for success is the wrong nearby case.
Recurring specifics. Zero means success, non-zero means failure. The code is checked with $? in shell or the runner's own step-status mechanism. Some items note that a turn-truncated run may still exit non-zero or may not, so gating on the code alone is necessary but not always sufficient (see Rule 11).
Proposal. grep the stdout for an error word.
Why it attracts. seems direct.
Why it fails. wording drifts; the code is the stable signal.
When it would be right. as a secondary heuristic only.
Proposal. assume the step passed because output appeared.
Why it attracts. output implies work.
Why it fails. partial output can accompany failure.
When it would be right. never as the sole gate.
- Items sometimes note that a truncated run "may indicate truncation but does not necessarily fail the job unless the pipeline checks." The teaching point: the code is necessary, but for structured contracts you also assert the payload.
Failure gating must also assert a populated `structured_output` and a non-error `subtype`
When a run uses --output-format json with --json-schema, a successful-looking exit can still hide a contract violation. The robust gate checks both the exit code and the presence of a valid structured_output, and treats a schema-retry exhaustion as failure. A common pattern fails closed with a jq -e assertion.
claude -p "Emit the verdict" --output-format json --json-schema verdict.schema.json \
| jq -e '.structured_output' > /dev/null || exit 1The schema validation can exhaust its retries and the run can still terminate with output that lacks the validated object, or with a subtype indicating error_max_structured_output_retries. Gating only on the exit code would let a malformed or empty contract pass downstream, where the posting service then fails opaquely. Asserting structured_output closes that gap.
Boundary. If no schema is in use, there is no structured_output to assert; gate on the code and on the shape you can verify. The opposite answer (assert the field) is correct exactly when --json-schema is in play. Prompt-only JSON has no deterministic field to assert.
Recurring specifics. The assertion idiom is jq -e '.structured_output' || exit 1. The envelope carries a subtype; a value such as error_max_structured_output_retries signals schema failure. The flag combination is --output-format json plus --json-schema. (Uncertain: the exact subtype string and the draft version the validator uses are taken from the tested material and should be confirmed against current CLI docs by the authoritative collector.)
Proposal. gate only on exit code.
Why it attracts. simplest.
Why it fails. misses schema-retry exhaustion.
When it would be right. when no schema is used.
Proposal. gate on the free-text result.
Why it attracts. always present.
Why it fails. not the validated contract.
When it would be right. never for structured-gated steps.
- Items rotate the schema field names (
verdict/reasons,findings,file/line/severity/message). The gating rule is invariant: assert the validated object, not the prose.
Independent review instance beats self-review inside the authoring session
When the same session both generates code and reviews it, the reviewer retains the generation session's reasoning context: the tradeoffs it considered, the alternatives it rejected, the justifications it built. That context biases it toward its own decisions and makes it less likely to question them. The fix is a second, independent claude -p invocation for review that receives only the code, the diff, and the standard, never the generating run's transcript or notes.
# Step 1: generate (session A)
claude -p "Implement the authentication middleware"
# Step 2: review (session B, independent, no shared context)
claude -p "Review the authentication middleware for security issues, error handling gaps, and edge cases"Self-review is weakened by commitment and consistency pressure baked into the generation trace. An independent instance evaluates the artifact on its own merits and is free to flag what the author would have defended. The effect is measurable: a production incident where a migration silently dropped a NOT NULL constraint that the same-instance review should have caught is the canonical evidence of the failure mode.
Boundary. For trivial changes or when speed outweighs rigor, a same-session review is acceptable. The opposite answer (independent instance) becomes right when the change is security- or correctness-sensitive and the cost of a missed flaw is high. The authoring session's notes on uncertainty can be passed to the reviewer as extra context without passing its full reasoning.
Recurring specifics. The pattern is "two claude -p invocations, no shared session." The review invocation gets the diff and file contents, optionally the standard, and at most selected uncertainty notes, never the transcript. The concept connects to multi-instance review and context-management domains but is tested here in CI scenarios.
Proposal. add a PostToolUse hook in the same process to review after each Write.
Why it attracts. automated and inline.
Why it fails. still the same session, same bias.
When it would be right. never as a substitute for independent review.
Proposal. call the API directly with a hardcoded system prompt.
Why it attracts. bypasses the tool.
Why it fails. reimplements file and tool infrastructure; the bias question is unchanged.
When it would be right. only when the tool is genuinely unwanted.
- Some items fold review into a multi-pass sequence inside one run; that still carries the bias. Others pass the generating run's "least sure" notes to an independent reviewer; that is allowed and even helpful, because the reasoning trace is excluded.
Incremental review context suppresses duplicate findings across pushes
A review that runs on every push with no memory of prior runs re-derives the entire PR from scratch and re-emits the same findings each time. Fixed issues drop out naturally, but issues the developer saw and chose not to act on reappear on every push, eroding trust. The fix is to carry prior findings into the next run and instruct the agent to report only new or still-unaddressed issues.
claude -p --output-format json \
"Review this PR. Previous findings:
${PREVIOUS_FINDINGS}
Report ONLY new issues and issues still present. Do not re-report addressed ones."Without prior context, the run cannot distinguish a genuinely new problem from a known, deliberately-accepted one, so it flags both. Supplying the prior findings lets the model collapse the already-known set and surface only signal. Developer trust depends on the signal-to-noise ratio of the comments.
Boundary. For a first-ever review there is no prior context, so full analysis is correct. The opposite answer (carry prior findings) becomes right on the second and later pushes. A from-scratch re-scan is the right nearby case only when no history exists.
Recurring specifics. The mechanism is prompt-supplied prior findings plus an instruction to report only new or unaddressed. The stored artifact is typically the previous structured_output or a file. The goal is to stop duplicate inline comments.
Proposal. scope each run to only changed files.
Why it attracts. also reduces noise.
Why it fails. that is the incremental-context-window technique, a different rule; it does not remove cross-push duplicates.
When it would be right. for context-window overflow, not duplicate comments.
Proposal. raise the model or add more passes.
Why it attracts. more effort finds more.
Why it fails. does not address repetition.
When it would be right. never for this purpose.
- Items ask whether storing findings and re-injecting them changes the correct answer. It does: without it, duplicates; with it, signal. The delta is the teaching point.
`CLAUDE.md` supplies project context to a CI-invoked run exactly as interactively
The CLI reads project CLAUDE.md files in CI just as it does in an interactive session. That file is therefore the channel for feeding a headless run the project's testing standards, available fixtures, review criteria, and existing coverage, so generated tests and reviews follow team patterns instead of producing generic boilerplate.
# .claude/CLAUDE.md - CI-relevant section
## Testing Standards
- Use the factory pattern from test/factories/ for data creation
- Integration tests connect via test/setup/db.ts
- Do not test private implementation details; test public API contracts
- Coverage target: 80% branch coverage for new code
- Available fixtures: test/fixtures/users.json, test/fixtures/orders.jsonA headless run has no developer to brief it on conventions. CLAUDE.md is the persistent, version-controlled briefing the tool loads automatically. Without it, test generation suggests duplicates of existing tests and reviews apply generic criteria; with it, output matches the repo's actual standards.
Boundary. If the task is generic and repo-agnostic, CLAUDE.md adds little. The opposite answer (supply it) is correct whenever the output must match project-specific conventions or avoid duplicating existing work. A missing CLAUDE.md does not hang the run; it only weakens relevance.
Recurring specifics. The file is CLAUDE.md (often under .claude/). It carries testing standards, fixtures, review severity criteria, and coverage notes. It is read in CI without special flags. It is advisory context, not an enforcement layer (see Rule 20).
Proposal. a prompt instruction replaces CLAUDE.md.
Why it attracts. inline.
Why it fails. not version-controlled or persistent; repeats per call.
When it would be right. for one-off overrides only.
Proposal. missing CLAUDE.md hangs the run.
Why it attracts. sounds like a hard dependency.
Why it fails. the run proceeds without it.
When it would be right. never.
- Items test whether including existing tests in context avoids duplication; that is the same principle applied to test generation. The constant: give the run the project's context.
`--dangerously-skip-permissions` removes every permission prompt for trusted CI
In a non-interactive run, any tool call that would require a permission prompt has no human to satisfy it and the job hangs. The --dangerously-skip-permissions flag removes all permission prompts so every tool call proceeds autonomously. It is the intended flag for trusted CI where a human cannot respond.
Permission prompts exist for interactive safety. In unattended execution they are an unreachable gate, so the only ways to avoid a hang are to pre-approve tools (Rule 18) or to skip permissions entirely. The flag is named to signal the risk: it should never be used where repository content is untrusted, because malicious content could trigger harmful tool calls.
Boundary. In an interactive session with possibly hostile repo content, the flag is dangerous and wrong; default permission prompts protect the user. The opposite answer (use it) is correct only in trusted, unattended CI. A "Tool use not permitted in non-interactive mode" error is the canonical signal that the flag is missing.
Recurring specifics. The flag is --dangerously-skip-permissions. It pairs with -p. It is the fix for "Tool use not permitted" and for hangs caused by permission prompts. It is distinct from the --permission-mode bypassPermissions value (Rule 16).
Proposal. --yes auto-accepts prompts.
Why it attracts. familiar.
Why it fails. not a real flag.
When it would be right. never.
Proposal. --no-confirm skips confirmation.
Why it attracts. familiar.
Why it fails. not a real flag.
When it would be right. never.
Proposal. --headless enables autonomy.
Why it attracts. sounds right.
Why it fails. not a real flag.
When it would be right. never.
- Items rotate the distractor flags (
--yes,--no-confirm,--headless); the correct answer is invariant. Some pair it with-pto emphasize both interactivity and permissions must be solved.
`--permission-mode bypassPermissions` is the mode spelling of the same autonomous behavior
The --permission-mode flag accepts a mode value, and bypassPermissions is the value that approves everything, equivalent in effect to --dangerously-skip-permissions. Both remove prompts; one is a flag, the other is a mode argument. In CI, either achieves autonomous execution, though the mode form composes with the permission system's evaluation order.
The permission system is centered on a mode plus rule lists. bypassPermissions is the mode that skips prompts, so it is the natural spelling when configuring permissions declaratively. The standalone flag is a shorthand for the same effect. Understanding both lets a candidate read either form in a config.
Boundary. Where fine-grained control is wanted, bypassPermissions is too blunt; acceptEdits, plan, auto, dontAsk, or default are preferable (Rules 17, 20). The opposite answer (use bypassPermissions) is correct only when full autonomy is intended and the environment is trusted.
Recurring specifics. The mode values are default, acceptEdits, plan, auto, dontAsk, and bypassPermissions. The flag is --permission-mode <mode>. bypassPermissions approves all tool calls. It is the mode-level equivalent of the standalone flag.
Proposal. --dangerously-skip-permissions and --permission-mode bypassPermissions conflict.
Why it attracts. two controls look redundant.
Why it fails. they are two spellings of the same behavior.
When it would be right. never; they are compatible.
Proposal. bypassPermissions is a flag name.
Why it attracts. looks like one.
Why it fails. it is a value of --permission-mode.
When it would be right. never.
- Items may present only the mode form or only the flag form; the candidate must recognize either as valid. The trap is believing one invalidates the other.
`--permission-mode dontAsk` plus `permissions.deny` blocks tools even in permissive modes
Deny rules in permissions.deny are evaluated early in the permission order and block matching tool calls even when the active mode is bypassPermissions or dontAsk. So a CI step can run autonomously yet still forbid a dangerous command pattern such as a forced push. The dontAsk mode auto-approves most calls but still honors deny rules.
{
"permissions": {
"deny": ["Bash(git push --force*)"]
}
}Permissive modes answer "allow" to prompts, but deny rules sit ahead of the mode in the evaluation order, so a matching deny wins regardless of mode. This lets a team grant autonomy while carving out irreversible operations that must never run unattended. The combination is the secure default for CI.
Boundary. If the step must be fully autonomous with no carve-out, bypassPermissions without deny rules is enough. The opposite answer (add deny rules) is correct when specific dangerous actions must be impossible even in autonomous mode. A missing deny rule means the dangerous command can run.
Recurring specifics. Deny rules use the Bash matching syntax and must cover the command forms expected (long flags, reordered arguments). dontAsk auto-approves but respects deny. The evaluation order is hooks, deny, ask, mode, allow (Rule 22). Deny blocks even in bypassPermissions.
Proposal. a CLAUDE.md instruction "never force-push.".
Why it attracts. advisory and easy.
Why it fails. not enforced; the model may comply but is not blocked.
When it would be right. as defense in depth only.
Proposal. a PostToolUse audit hook.
Why it attracts. observability.
Why it fails. fires after the action; too late to prevent.
When it would be right. as a detective control alongside prevention.
- Items ask whether
dontAskoverrides a deny rule. It does not. They ask whetherbypassPermissionsoverrides a deny rule. It does not. The invariant: deny wins over mode.
`--allowedTools` pre-approves listed tools; it grants, it does not restrict the surface
The --allowedTools flag names tools that run without a permission prompt. It is a grant list: the named tools are approved up front so an unattended run never blocks on them. Critically, it pre-approves; it does not by itself shrink the overall set of tools the agent can reach. Restricting the surface is a different mechanism (Rule 19).
A headless run needs certain tools to proceed without prompts. Listing them as allowed removes the interactive gate for those calls. Because the flag is about approval, not exclusion, a session can still reach other tools through other paths; to forbid a tool you use --disallowedTools or --tools.
Boundary. When the goal is "never prompt for these specific tools," --allowedTools is correct. The opposite answer (use it to block a tool) is wrong; for blocking you need --disallowedTools or a permissions.deny rule. A fan-out over many files commonly pairs --allowedTools with -p so each invocation runs unattended.
Recurring specifics. The flag is --allowedTools "<rules>", space-separated. Rules look like Bash(git diff:*), Read, Edit. It grants approval, not restriction. It is distinct from --tools, which limits availability.
Proposal. --allowedTools removes a tool from context.
Why it attracts. "allowed" sounds limiting.
Why it fails. it approves, it does not remove.
When it would be right. never for removal.
Proposal. configure it in settings.json to exclude Bash.
Why it attracts. config-like.
Why it fails. that is a deny rule, not allowedTools.
When it would be right. when the intent is approval, not exclusion.
- Items ask whether
--allowedToolsrestricted to[Read, Grep, Glob]lets the agent fix a found vulnerability. It does not: withoutWrite/Editit can only report. That is the hard-boundary rule (19), tested through the grant flag.
`--disallowedTools` and `--tools` remove capability and form a hard boundary
Where --allowedTools grants, --disallowedTools denies named tools and --tools restricts which built-in tools are available at all. These form a hard boundary: a tool not in the allowed or available set cannot be invoked, regardless of what the model decides. For a read-only security audit, restricting to [Read, Grep, Glob] means the agent can only report, never modify.
Enforcement lives in the client, not in the model's compliance. A configuration-time restriction removes the capability entirely, so a request to "fix the vulnerability" simply cannot be acted on. This is the correct control when the task must be analysis-only and no edit may occur.
Boundary. If the task requires edits, --tools must include Write/Edit. The opposite answer (restrict) is correct when the mandate is read-only or scoped to a directory. A CLAUDE.md instruction to "not modify files" is the wrong nearby case: it is advisory, not a boundary.
Recurring specifics. The flags are --disallowedTools "<rules>" and --tools "Bash,Edit,Read". Restriction is configuration-time and hard. A [Read, Grep, Glob] audit cannot write. Scope can be limited to a directory for the payment-module case.
Proposal. tell the model in the prompt not to edit.
Why it attracts. simple.
Why it fails. not enforced; under pressure it may edit.
When it would be right. never as the sole control.
Proposal. make the whole repo read-only at the OS level.
Why it attracts. strong.
Why it fails. breaks git and other steps; the tool-level flag is the right layer.
When it would be right. only as additional hardening.
- Items test whether the agent "expands permissions at runtime." It cannot: permissions are configuration-time. The invariant: a missing tool in the list means no invocation.
Advisory flags (`--read-only` is uncertain) versus enforced controls
Some evidence presents a --read-only flag that restricts a session to analysis without file changes. This is an advisory-or-enforced control depending on whether the flag is real. The reliable contrast is between advisory controls (prompt text, CLAUDE.md instructions) and enforced controls (permission deny rules, tool restrictions, hooks). Advisory controls shape behavior but are not guaranteed; enforced controls apply regardless of model choice.
(Uncertain: the --read-only flag appears in the tested material as the documented review-only mechanism, but the authoritative CLI reference reviewed for this task lists read-only behavior via --permission-mode plan rather than a standalone --read-only flag. The exact official spelling should be confirmed by the authoritative collector; treat --read-only as unverified.)
In an unattended run there is no human to catch a bad decision, and repository files can carry prompt-injection attempts. A control the model can talk its way past is insufficient against injected instructions. Enforced layers (deny rules, hooks, tool removal) are the ones that actually prevent the action.
Boundary. Advisory guidance is fine as defense in depth when a human or a hard control backs it. The opposite answer (rely on advisory) is wrong when the action is consequential and unattended. The nearby correct case is a hard tool boundary or a deny rule.
Recurring specifics. Advisory: CLAUDE.md, prompt instructions. Enforced: permissions.deny, --tools, PreToolUse hooks. Plan mode is a read-only workflow control (Rule 28 context). The --read-only flag is unverified here.
Proposal. instruct in CLAUDE.md not to modify.
Why it attracts. persistent.
Why it fails. advisory, not enforced.
When it would be right. only with a hard control behind it.
Proposal. a restricted API key enforces read-only.
Why it attracts. key-level sounds strong.
Why it fails. API keys lack tool-level granularity.
When it would be right. never for tool restriction.
- Items rotate advisory vs enforced across many surfaces; the invariant is "enforced wins when unattended."
PreToolUse and PostToolUse hooks are deterministic enforcement versus detective controls
Hooks run around tool calls. A PreToolUse hook receives the tool name and input as JSON on stdin and can block the call: exiting with code 2 blocks it outright and returns the stderr text to the agent as feedback, while exiting with code 1 is a non-blocking error that lets the call proceed. A PostToolUse hook observes the call after it ran and can only report, not prevent.
#!/usr/bin/env bash
# PreToolUse hook: block forced pushes
read -r input
echo "$input" | grep -q "git push --force" && exit 2
exit 0Prevention must happen before the tool executes. A PreToolUse hook that exits 2 stops the command before any side effect. A PostToolUse hook is a detective control: by the time it fires, the action reached the remote. The distinction is why hooks pair with deny rules for prevention and with logging for audit.
Boundary. When the goal is audit or metrics, a PostToolUse hook is correct. The opposite answer (use PreToolUse to block) is correct when the action must never happen. Exit code 1 is the trap: it is non-blocking, so a hook meant to enforce must use 2.
Recurring specifics. PreToolUse exit 2 blocks; exit 1 is non-blocking. The hook gets tool name and input as JSON on stdin. PostToolUse is detective only. Hooks run before deny/ask/mode/allow in the evaluation order.
Proposal. a weekly log audit catches the bad push.
Why it attracts. observability.
Why it fails. after the fact.
When it would be right. as a detective control only.
Proposal. exit 1 to block.
Why it attracts. error sounds blocking.
Why it fails. 1 is non-blocking; use 2.
When it would be right. never for blocking.
- Items ask whether a PostToolUse hook prevents the violation. It does not. They ask whether exit
1blocks. It does not. Both are traps around the same rule.
The permission evaluation order is hooks, then deny, then ask, then mode, then allow
When a tool call is requested, the client evaluates controls in a fixed order: hooks first, then deny rules, then ask rules, then the active permission mode, then allow rules. This ordering explains why deny rules block even in bypassPermissions, why ask rules force a confirmation even in dontAsk, and why a hook can deny what an allow rule would have approved.
The order encodes defense in depth: the earliest, most deterministic layers win. Hooks can apply rich logic a glob cannot; deny rules declaratively forbid known patterns; ask rules carve out consent-critical operations; the mode sets the default; allow rules pre-approve safe calls. Placing deny and ask ahead of the mode means permissive modes cannot silently auto-approve a carved-out action.
Boundary. When no rule matches, the mode decides. The opposite answer (mode decides everything) is wrong because deny and ask sit ahead of it. An MCP server can also force a prompt via _meta["anthropic/requiresUserInteraction"], which ignores matching allow rules and prompts even in permissive modes; in dontAsk that call is denied rather than executed.
Recurring specifics. Order: hooks, deny, ask, mode, allow. Deny blocks even in bypassPermissions. Ask forces confirmation even in dontAsk. MCP requiresUserInteraction prompts regardless of mode and is denied in dontAsk. Allow rules pre-approve safe commands.
Proposal. bypassPermissions overrides a deny rule.
Why it attracts. "bypass" sounds absolute.
Why it fails. deny is earlier in the order.
When it would be right. never.
Proposal. an allow rule auto-approves the deploy.
Why it attracts. pre-approved.
Why it fails. an ask rule for the deploy sits ahead and forces confirmation.
When it would be right. when no ask rule matches.
- Items test whether
dontAskoverrides an ask rule (no) or whetherbypassPermissionsoverrides deny (no). The invariant is the order.
`ANTHROPIC_API_KEY` must be present or the run fails immediately in CI
The most common cause of an immediate CI failure is a missing ANTHROPIC_API_KEY environment variable. The headless run is designed for automation and will execute, but without a credential it cannot reach the model and fails before producing output. Setting the variable in the pipeline environment is the prerequisite to every other flag working.
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}The CLI needs a credential to call the backend. In an interactive session a user may have signed in, but a runner has no session, so the key must be injected. The failure is immediate and total, distinct from a hang, which signals a missing -p.
Boundary. In an interactive session where a user has authenticated, the key may be absent and the tool still works via the subscription. The opposite answer (key required) is correct in unattended CI. A hang is never caused by a missing key; a missing key fails fast, a missing -p hangs.
Recurring specifics. The variable is ANTHROPIC_API_KEY. It is supplied as a secret in the pipeline environment. Its absence is the top cause of immediate failure. It is separate from interactivity and from permission flags.
Proposal. the hang is due to a missing key.
Why it attracts. auth sounds related.
Why it fails. a missing key fails fast, not hangs.
When it would be right. never; a hang means missing -p.
Proposal. file access permissions cause the failure.
Why it attracts. plausible.
Why it fails. the key is the documented common cause.
When it would be right. only in narrow repo cases.
- Items pair the key question with the
-pquestion to separate "fails fast" from "hangs." The distinction is the teaching point.
The authentication precedence chain ranks an API key above an interactive subscription
When more than one credential is available, the CLI resolves which to use through a fixed precedence chain, not by recency. The order runs from cloud-provider credentials (when a CLAUDE_CODE_USE_* variable is set), to ANTHROPIC_AUTH_TOKEN, to ANTHROPIC_API_KEY, to apiKeyHelper, to CLAUDE_CODE_OAUTH_TOKEN, and only then to the subscription OAuth session created by interactive sign-in. An explicit, environment-level key therefore outranks an interactive subscription.
Explicit credentials are deterministic for headless usage: a script's behavior cannot be silently changed by whatever account a human happens to be logged in with. The tradeoff is that a key exported globally in a shell profile leaks into interactive sessions and shadows the subscription, causing unexpected billing against the key.
Boundary. If a machine should use the subscription for interactive work but a key for CI, the key must be scoped to the CI environment only, not exported globally. The opposite answer (re-run sign-in to fix billing) is wrong because OAuth sits at the bottom of the chain and cannot override a key above it. CLAUDE_CODE_OAUTH_TOKEN and apiKeyHelper also rank below the key and cannot override it.
Recurring specifics. The chain is CLAUDE_CODE_USE_* (cloud), ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY, apiKeyHelper, CLAUDE_CODE_OAUTH_TOKEN, then /login subscription. Explicit env credentials outrank interactive sign-in. Scoping the key to CI avoids shadowing the subscription.
Proposal. re-run /login to use the subscription.
Why it attracts. re-auth seems to switch.
Why it fails. OAuth is lowest priority; the key still wins.
When it would be right. never while the key is exported.
Proposal. set CLAUDE_CODE_OAUTH_TOKEN to override the key.
Why it attracts. token sounds higher.
Why it fails. it ranks below the key.
When it would be right. never.
- Items rotate which credential is proposed to override the key; none below it can. The invariant is the chain order.
Secret handling uses protected stores and `permissions.deny` rules, never committed keys
Pipeline credentials must come from protected secret stores or workload identity, scoped to the minimum permissions the job needs, and must never appear in the workflow file, in logs, or in prompt text. Sensitive files such as .env are kept out of raw code and blocked at the tool layer with a permissions.deny rule like Read(.env) and Read(.env.*). Plan mode adds oversight for sensitive sessions.
{
"permissions": {
"deny": ["Read(.env)", "Read(.env.*)"]
}
}Committed credentials can be copied and abused; debug logs may be retained and visible; shared broad credentials weaken attribution and rotation. A permissions.deny rule is enforced by the client, so even a prompt instructing the agent to read the secret is blocked. Workload identity removes the long-lived key entirely for supported providers.
Boundary. For a quick local experiment a developer might export a key temporarily, but in shared repositories and automation the key must be in a protected store. The opposite answer (commit the key in the workflow) is always wrong. Diagnosing auth without leaking the secret means checking the configuration scope, not printing the value.
Recurring specifics. Secrets come from protected stores or workload identity, least-privilege scoped. Never commit, log, or prompt the secret. permissions.deny patterns like Read(.env) block reads. Plan mode adds human oversight. The authentication must remain reviewable and narrowly scoped.
Proposal. commit the key so every runner can access it.
Why it attracts. seems convenient.
Why it fails. copyable and abusable.
When it would be right. never.
Proposal. a CLAUDE.md rule never to read .env.
Why it attracts. advisory.
Why it fails. not enforced; use permissions.deny.
When it would be right. only with the deny rule behind it.
Proposal. store keys in a separate .env and prompt the agent to ignore it.
Why it attracts. separation.
Why it fails. still readable without a deny rule.
When it would be right. with the deny rule added.
- Items rotate the fictional company and the action (doc site, notification, data pipeline, identity gateway, migration) but the answer is invariant: protected secret or workload identity, least privilege, never in prompts or logs.
`--max-turns <n>` caps reasoning loops and bounds cost and latency
Even in non-interactive mode the agent may need several reasoning-action cycles to gather information and produce a complete answer. The --max-turns flag caps how many cycles it may perform before finalizing. This bounds both runtime and API usage, preventing a runaway process in CI. The corresponding environment variable is ANTHROPIC_MAX_TURNS.
Without a cap, a stuck or expansive agent can loop indefinitely, burning time and tokens. The cap forces termination with whatever output it has produced so far. When the cap is reached, the run stops the loop and emits the partial result; the exit code may indicate truncation but does not necessarily fail the job unless the pipeline checks completion.
Boundary. For a simple one-shot task, a low cap (even 1) suffices. The opposite answer (raise the cap) is correct when the task genuinely needs multiple cycles, such as test generation that must read, write, and re-run. Setting it too high risks runaway cost; too low risks incomplete output. (Uncertain: the tested material states -p auto-sets --max-turns to 1; this is not confirmed in the authoritative reference reviewed here and should be verified by the authoritative collector.)
Recurring specifics. The flag is --max-turns <n>; the env form is ANTHROPIC_MAX_TURNS. Reaching the cap stops the loop and emits partial output. Test generation commonly uses a small cap like 5. A hang is not caused by the cap; a missing -p is.
Proposal. the agent ignores --max-turns and continues.
Why it attracts. sounds defiant.
Why it fails. the cap is enforced; it stops.
When it would be right. never.
Proposal. --max-turns sets the max files to print.
Why it attracts. "print" suggests output.
Why it fails. it caps reasoning cycles, not file count.
When it would be right. never.
- Items ask what happens at the cap (stops with partial output) and whether it fails the job (only if the pipeline gates on completion). The invariant: the cap is enforced.
The Batch API saves cost but has no latency SLA; real-time suits blocking checks
The Message Batches API offers a substantial cost saving (roughly half) but processes in up to a day with no guaranteed latency. This creates a decision boundary: pre-merge checks are blocking, developers wait for results, so they need real-time synchronous calls; overnight or weekly non-blocking analysis is latency-tolerant, so the Batch API fits.
# Blocking pre-merge check: real-time, developers wait
claude -p "Review this PR and block merge on findings" --output-format json
# Overnight technical-debt report: batch, latency-tolerant
# submit the same prompt set to the Batch API endpointA blocking check that misses its latency window stalls the merge queue and frustrates developers; the Batch API's up-to-24-hour window makes it unsuitable there. A nightly report reviewed the next morning cares about cost, not speed, so the saving is pure gain. The exam tests the blocking-versus-non-blocking distinction directly.
Boundary. If a check is truly non-blocking (overnight audit, weekly scan, nightly test generation), the Batch API is correct. The opposite answer (batch for pre-merge) is wrong because developers cannot wait a day. Real-time is correct wherever a human is blocked on the result.
Recurring specifics. The saving is about half; the window is up to 24 hours; there is no latency SLA. Pre-merge, overnight, weekly, and nightly map to real-time versus batch. The distinction is blocking versus tolerant.
Proposal. use the Batch API for pre-merge checks to save cost.
Why it attracts. cheaper.
Why it fails. no latency guarantee; developers wait.
When it would be right. never for blocking checks.
Proposal. real-time for the weekly audit.
Why it attracts. simpler.
Why it fails. wastes the saving; latency does not matter there.
When it would be right. only if simplicity outweighs cost.
- Items rotate the workflow type; the invariant is blocking needs real-time, tolerant needs batch.
`--system-prompt` replaces, `--append-system-prompt` adds, with file variants
The CLI offers four system-prompt controls. --system-prompt "<text>" replaces the entire default system prompt; --system-prompt-file <path> replaces it with a file's contents; --append-system-prompt "<text>" appends text to the default; --append-system-prompt-file <path> appends a file's contents. In CI, append is usually right when the agent should stay a coding assistant that also follows extra rules, keeping default tool guidance and safety; replace is right when the identity or permission model differs from the tool's.
Replacing drops the entire default prompt, so the caller owns everything the task still needs (tool guidance, safety, conventions). Appending keeps that default and adds only the delta, which is safer for a coding agent in a pipeline nobody watches. The file variants let large prompts live in version control rather than inline.
Boundary. When the pipeline runs a non-coding agent with a different identity, replace is correct. The opposite answer (append) is correct when the agent should remain a coding assistant. A prompt-only JSON instruction is probabilistic and may drift; the system-prompt flags are deterministic setup, not output enforcement.
Recurring specifics. The four flags are --system-prompt, --system-prompt-file, --append-system-prompt, --append-system-prompt-file. Replace drops defaults; append keeps them. Append is the common CI choice for extra rules.
Proposal. append a strong JSON instruction to guarantee output shape.
Why it attracts. looks complete.
Why it fails. probabilistic; use --json-schema for the contract.
When it would be right. never as the sole enforcement.
Proposal. replace to add a rule.
Why it attracts. seems to include it.
Why it fails. drops default safety and tool guidance.
When it would be right. only when a new identity is intended.
- Items test append-versus-replace directly; the invariant is append keeps defaults, replace drops them.
`--bare` skips discovery for a fast, predictable scripted run
The --bare flag starts a minimal session that skips auto-discovery of hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.md. This makes a scripted call start faster and behave predictably, leaving the agent with only the Bash and file read/edit tools. It is the right choice when a fast, deterministic run does not need project configuration loaded.
Discovery adds startup cost and can pull in project-specific behavior that a scripted job does not want. For a tightly scoped automated step, skipping it yields a lean, repeatable invocation. The tradeoff is that project context from CLAUDE.md and MCP tools is absent, so the prompt must supply whatever context is needed.
Boundary. When the step benefits from CLAUDE.md context or MCP tools, --bare is wrong. The opposite answer (skip discovery) is correct for a fast, self-contained scripted run. A review that needs project criteria should not use --bare unless those criteria are in the prompt.
Recurring specifics. --bare skips hooks, skills, plugins, MCP, auto memory, and CLAUDE.md. It leaves Bash and file read/edit tools. It speeds startup and improves predictability for scripts.
Proposal. use --bare to load project context faster.
Why it attracts. sounds lean.
Why it fails. it skips project context.
When it would be right. never; it removes it.
Proposal. --bare enables more tools.
Why it attracts. minimal sounds permissive.
Why it fails. it restricts to Bash and file tools.
When it would be right. never.
- Items ask whether
--bareloadsCLAUDE.md; it does not. The invariant is skip-discovery.
`--model` selects the session model for a headless run
The --model flag sets the model the session uses, accepting either a short alias or a full model name. In CI this lets a team pick a lighter model for cheap mechanical jobs or a stronger one for hard reasoning, independent of any interactive default.
Different CI steps have different cost and quality needs. A bulk documentation pass may use a cheaper model; a security review may use a stronger one. The flag makes that choice explicit and reproducible per step rather than inherited from a global setting.
Boundary. If the default model is acceptable, the flag is unnecessary. The opposite answer (set it) is correct when cost or quality must be tuned per step. The alias must be a valid one; an unknown name fails the run.
Recurring specifics. The flag is --model <alias|name>. It accepts an alias or a full name. It is independent of interactivity. It pairs with -p in scripts.
Proposal. set the model via CLAUDE.md.
Why it attracts. config-like.
Why it fails. not the model-selection mechanism; use --model.
When it would be right. never for selection.
Proposal. the model is chosen by the API key.
Why it attracts. key sounds tied to account.
Why it fails. key is auth, not model.
When it would be right. never.
- Items may ask whether a larger model fixes a reliability failure. It changes probability, not possibility; the real fix is an enforced control. The invariant: model is a quality/cost dial, not a safety control.
`--verbose` emits full turn-by-turn output for debugging a failing job
The --verbose flag produces full turn-by-turn output, showing each reasoning and tool step. In CI this is primarily a debugging aid: when a headless review misbehaves, verbose output reveals which turn failed or looped.
A non-interactive run hides its intermediate steps by default, so a silent failure is hard to diagnose. Verbose output restores visibility into the agent's loop, which is exactly what an engineer needs when a step produces no usable result or exits unexpectedly.
Boundary. For normal runs, verbose output is noise that bloats logs. The opposite answer (use it) is correct when debugging a failing or partial run. It does not change behavior, only visibility.
Recurring specifics. The flag is --verbose. It shows turn-by-turn detail. It is a debug aid, not a behavior change. It pairs with --output-format json for diagnosed structured runs.
Proposal. --verbose fixes a hang.
Why it attracts. more info sounds like a fix.
Why it fails. it only adds logging; the hang needs -p.
When it would be right. never as a fix.
Proposal. --verbose changes the output format.
Why it attracts. output-related.
Why it fails. format is --output-format; verbose is detail.
When it would be right. never.
- Items ask whether verbose alters execution; it does not. The invariant: visibility, not behavior.
Subagents inherit `bypassPermissions` and cannot be separately constrained
When a headless session runs in bypassPermissions mode, any subagents it spawns inherit that mode and cannot be separately constrained to a stricter permission set. This means a top-level autonomous mode propagates downward, so a dangerous capability granted at the top is also available to delegated work unless blocked by a deny rule that applies regardless of mode.
Subagents are extensions of the parent session's authority. If the parent skipped permissions, the child has the same latitude by default. The only reliable way to keep a dangerous action impossible for subagents too is a permissions.deny rule, which blocks even in bypassPermissions and therefore also blocks the inherited mode.
Boundary. If the parent runs in a stricter mode, subagents inherit that stricter mode. The opposite answer (subagents can be locked down independently) is wrong under bypassPermissions; use deny rules for cross-mode prevention. A missing deny rule means inherited autonomy reaches the subagent.
Recurring specifics. Subagents inherit bypassPermissions. They cannot be separately constrained to stricter mode. Deny rules block regardless of mode and therefore cover subagents. The evaluation order (Rule 22) still applies to subagent calls.
Proposal. set a stricter mode on the subagent.
Why it attracts. granular.
Why it fails. inheritance wins under bypassPermissions.
When it would be right. never under that mode.
Proposal. rely on the prompt to restrict the subagent.
Why it attracts. advisory.
Why it fails. not enforced.
When it would be right. never as the sole control.
- Items test whether subagent autonomy can be capped per-child; it cannot under
bypassPermissions. The invariant: deny rules are the cross-mode control.
Streaming output must be consumed as a sequence, not parsed as one object
When --output-format stream-json is used, stdout is a stream of JSON objects emitted over time, not a single JSON document. A consumer must read and parse each object as it arrives (or accumulate then parse per object) rather than attempting to parse the whole stream as one JSON value, which would fail because the concatenation is not valid JSON.
claude -p "Stream the review" --output-format stream-json | while IFS= read -r line; do
echo "$line" | jq -e '.type' > /dev/null || true
doneStreaming trades a clean single payload for progressive delivery. The contract changes from "one envelope" to "many events." A parser written for json will break on stream-json because it expects one root object. The consumer must adapt its read loop.
Boundary. If the consumer only acts after the full result, json is simpler and the parser is trivial. The opposite answer (stream) is correct when progressive display or checkpointing matters. Mixing the two parsers is the common bug.
Recurring specifics. stream-json is a sequence of objects. json is one envelope. The flag is --output-format, not a separate --stream. --json-schema still applies in print mode regardless of streaming.
Proposal. parse stream-json as one JSON document.
Why it attracts. it is JSON.
Why it fails. it is a stream of objects.
When it would be right. never.
Proposal. streaming removes the need for -p.
Why it attracts. modern-sounding.
Why it fails. interactivity is still governed by -p.
When it would be right. never.
- Items ask whether streaming changes the schema requirement; it does not. The invariant: stream is many objects, single payload is one.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| -p flag | CLAUDE_HEADLESS, --batch, stdin redirection | -p is the documented non interactive flag that processes the prompt and exits. The others do not exist or do not properly disable interactive mode. |
| --output-format json | --output-format text | json is machine parseable with result plus structured_output for programmatic posting, text is human readable. CI pipelines need json, interactive sessions typically use text. |
| --json-schema validation | --append-system-prompt schema instruction | --json-schema validates against draft-07 deterministically with internal re prompt. Prompt only instructions are probabilistic and may drift, not suitable as a blocking gate. |
| Same session self review | Independent review instance | Same session retains generation reasoning and is less likely to question its own choices. Independent approaches the code fresh and is more thorough, so CI should generate and review in separate invocations. |
| Real time synchronous API | Message Batches API | Synchronous has seconds to minutes latency for blocking CI checks. Batches has up to 24 hour window with no SLA for overnight reports and weekly audits only. |
| --append-system-prompt | --system-prompt replace | Append keeps default Claude Code behavior plus extra rules. Replace drops the default prompt and its safety guidance, used only when the pipeline identity differs from Claude Code coding assistant. |
| --permission-mode dontAsk | --allowedTools for specific tools | dontAsk auto denies anything not allow listed, useful for locked down read and report CI. allowedTools auto approves specific tools while most others remain gated, useful when most tools should still be checked. |
Traps
The tempting answer. Set CLAUDE_HEADLESS equals true or add --batch or redirect stdin from /dev/null because the names sound plausible for headless.
Why it fails. None of those flags exist on Claude Code, the interactive gate is the only documented headless switch. Distractors that sound plausible are the most common wrong answer for the hanging CI scenario.
What is correct. Invoke claude -p or --print for every CI headless call, which switches to print mode, processes the prompt, emits to stdout, and exits with no keyboard required.
The tempting answer. Generate and review in one invocation to save cost because the same session already knows the code.
Why it fails. That session retains its own generation reasoning and justifications, reducing its ability to question the same decisions it just made. Independent review removes that bias and finds more of the issues the generation reasoning would justify away.
What is correct. Run separate claude -p invocations for generation and for review so the reviewer evaluates the code without carrying generation rationale.
The tempting answer. Move pre merge checks to Message Batches API for the 50 percent cost savings.
Why it fails. Pre merge is blocking where a developer cannot merge until the check completes, but Batches has up to 24 hour processing with no SLA, so the pipeline would wait unpredictably and block merges far beyond acceptable latency.
What is correct. Keep real time synchronous calls for blocking pre merge checks and reserve Batches for overnight technical debt reports and similar latency tolerant work.
The tempting answer. Re analyse the entire PR from scratch on every push because every run is already stateless.
Why it fails. Without prior findings every push re derives the same comments, fixed issues reappear and deliberately deferred issues are republished on every run, which erodes developer trust when the same five issues appear regardless of fixes.
What is correct. Store prior findings as a JSON artifact, include them in the next run, and instruct to report only new or still unaddressed issues.
The tempting answer. Enforce Never run destructive commands via --append-system-prompt because prompt instructions feel straightforward.
Why it fails. Prompt instructions are probabilistic and may drift under tool pressure, long context, or adversarial tool output, while permissions and validation flags enforce deterministically regardless of model choice.
What is correct. Gate destructive operations with --permission-mode dontAsk plus permissions deny rules, and gate output shape with --json-schema rather than prompt compliance alone.
The tempting answer. Run headless without project CLAUDE.md because CI feels like a separate mode.
Why it fails. Claude Code reads CLAUDE.md in CI just as interactively, and without the same testing standards, fixtures, and review criteria the headless run produces low value boilerplate rather than team patterned tests.
What is correct. Keep a CI relevant section in CLAUDE.md covering standards, fixtures, setup, and review criteria so headless runs share the same project knowledge as interactive sessions.