Last Step/Claude Code Configuration and Workflows20%
Domain 36 task statements20% of the exam

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.

On this page
  1. 3.1 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.
  2. 3.2 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.
  3. 3.3 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.
  4. 3.4 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.
  5. 3.5 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.
  6. 3.6 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.
The essentials
Everything below explains why each of these is true.
  1. Four CLAUDE.md locations exist: user at ~/.claude/CLAUDE.md, project at CLAUDE.md or .claude/CLAUDE.md, directory level in any subdirectory, and CLAUDE.local.md appended last at its level. All discovered files concatenate from root down, nothing shadows.
  2. Walk-up chain loads eagerly at launch from working directory up to filesystem root. Subdirectory CLAUDE.md and path-scoped rules load lazily only when Claude reads a file in that subtree or matching that pattern.
  3. @path/to/file is the import directive written directly in CLAUDE.md with no @import keyword. Relative paths resolve against the importing file, max depth four hops, code spans and fenced blocks are skipped.
  4. A flat file at .claude/commands/name.md and a skill at .claude/skills/name/SKILL.md both create slash command /name. The skill path is canonical because it supports supporting files, automatic discovery, and precedence when names collide.
  5. Skill frontmatter fields tested: context fork for isolation, allowed-tools to pre approve tools without prompting, argument-hint to prompt for missing inputs, disable-model-invocation true for explicit only.
  6. Path-scoped rules live in .claude/rules/name.md with YAML frontmatter paths array of globs such as star star slash star dot test dot ts. They load only when a read matches, one file covers test files across 50 plus directories.
  7. Project CLAUDE.md is shared via git. User CLAUDE.md is personal and never reaches a new clone. The new team member drift scenario is always user versus project scope.
  8. Plan mode is for ambiguous multi-file or architectural work with multiple valid approaches. Direct execution is for well understood changes with clear scope and known fix. Plan then execute is the hybrid for migrations across many files.
  9. Explore subagent keeps discovery output out of the main conversation. The benefit is context isolation, not parallelisation. Main agent exploration fills the main window and degrades subsequent responses.
  10. Concrete examples fix inconsistent interpretation first. Test driven iteration fixes complex transformations via failures. Interview pattern surfaces hidden considerations in unfamiliar domains. Batched feedback is for interacting issues, sequential for independent.
  11. In CI, -p is the non-interactive flag. Without it the job hangs waiting for keyboard. --output-format json plus --json-schema validates machine parseable findings in structured_output. Independent review instances beat self review in the same session.
  12. Settings enforce, CLAUDE.md guides. Permissions that must hold belong in settings.json deny rules or PreToolUse hooks, never solely in CLAUDE.md prose.
Task 3.118 min

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.

TierCanonical pathLives outside repoCommittedReaches every cloneLoaded with
Managed policymacOS: /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.jsonYes, machine imageVia MDM, Group Policy, Ansible, or managed-settings.jsonAll sessions on managed machines, cannot be excludedBefore user and project, same priority as managed file
User~/.claude/CLAUDE.mdYes, home directoryNeverOnly the owner, on any repositoryAt session start alongside project files
Project./CLAUDE.md or ./.claude/CLAUDE.md at repository rootNoYes, via gitEvery teammate after clone or pullAt session start
Local./CLAUDE.local.md alongside any CLAUDE.mdNo, but gitignoredConvention says no, add to .gitignoreOnly the owner for that checkout, not shared via gitAppended after sibling at same level
Directorysubdirectory/CLAUDE.md such as packages/api/CLAUDE.md, packages/web/CLAUDE.mdNoYesEvery teammateLazily, 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 any types, 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 component useState

# File: ./.claude/CLAUDE.md (also project tier, committed)

Architecture notes

  • src/lib/api.ts is the only place that makes fetch calls.
  • Error handling: wrap with AppError and include code.

# 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 in packages/api/src/services/.
  • Endpoints require authMiddleware; request and response schemas use camelCase with error.details array.

# File: ./packages/api/CLAUDE.local.md (local, gitignored, appended last at that level)

Local scratch

  • Test account: [email protected], staging DB at localhost: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 test before committing; tests live in __tests__/ adjacent to source.
  • Use describe and it blocks, not test() directly.
  • Imported by: .claude/CLAUDE.md hop 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 in packages/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.

settings.json
json
// 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

GuaranteeOwnerWhy that layer
CLAUDE.md text is present in context, ordered root down, local lastClaude Code client, filesystem walkWalk and claudeMdExcludes gate happen before the model sees context
Import inlined, relative resolution, four hops, span-safeClientExpansion at load time, not model reasoning
"Use 2 space indent" is usually followedModel, weighted by specificity and brevityUser-message guidance, no guarantee, may pick arbitrarily on conflict
"Never run Bash(scp *)" is never executedClient via settings.json permissions.deny deny-first evaluationEvaluated before tool execution, cannot be overridden by memory
"Run formatter after every Write" happensClient via hooks PostToolUseFires at fixed lifecycle event regardless of model choice
SDK session sees project memory and skillsApplication code via settingSourcesCaller selects tiers; SDK honors that selection
Managed policy always applies, cannot be excluded or overriddenInfrastructure plus clientManaged 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 claimDocumentation positionWhat to answer and why
More specific CLAUDE.md wins; user level overrides project level; inner file completely replaces root inside its directoryAll 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 itAnswer 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 itGuaranteed 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 executionAnswer 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 CodeReal 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 fencesAnswer @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 teammatesConfiguration loads automatically at session start by discovery; /memory and /context are diagnostic viewers that do not trigger loadingAnswer 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 chainSubdirectory files under the working directory load lazily only when Claude reads a file in that subdirectoryAnswer 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 conditionalImported files still load at launch and occupy the same tokens; to shrink context use path-scoped rules or skillsAnswer 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 setAnswer 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.

TopicWhy it matters for this taskPrimary lesson slug
Path-scoped rules with paths frontmatter and brace expansionCorrect fix for file-type scoping and for bloated always-loaded guidanceclaude-code-mdc-config
Rules without paths versus with paths (always-loaded vs lazy)Explains which files survive /compact and which reload on matching readconfiguration
Skills as on-demand loading distinct from CLAUDE.mdCorrect alternative when guidance is conditionally relevant, not per-sessionconfiguration
claudeMdExcludes walk filtering and CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD gateExplains why a valid file on disk is not in /contextconfiguration and claude-code-mdc-config
Auto memory at ~/.claude/projects/<project>/memory/ and autoMemoryEnabledPrevents misdiagnosing authored memory versus model-written memoryconfiguration
settings.json precedence and $schema plus enterprise keysShows that project policy ceiling is managed settings, not memoryconfiguration
Runtime permission modes plan, acceptEdits, auto, dontAsk, bypassPermissionsPaired with hierarchy in scenario questions about destructive accidentscore-capabilities
AGENTS.md interop via @AGENTS.md or symlinkExplains single-source interop when a repo already uses AGENTS.mdconfiguration

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.md and keep only genuinely universal text at the root; directory files load lazily only when Claude reads inside that package, so a session working only in packages/web never pays for packages/api/CLAUDE.md.
  • If relevance follows file type across non-contiguous locations such as */migrations/.sql scattered across db/migrations, services/auth/migrations, and tools/data/migrations, a directory file per folder duplicates guidance and still misses files; the correct mechanism is .claude/rules/ with paths frontmatter 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.

settings.json
json
// 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": {}
}
settings.json
json
// 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.

settings.json
json
// 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"
          }
        ]
      }
    ]
  }
}
terminal
bash
#!/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
terminal
bash
#!/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
terminal
bash
#!/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
  }
}'
terminal
bash
#!/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 0

What 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.

example.ts
typescript
// 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: camelCase for variables, PascalCase for types, file per component in src/components/
  • Error handling: wrap with AppError carrying code and details; never throw raw Error from handlers
  • Architecture: Zustand stores in src/stores/, never inline useState for shared state

Code Review Checklist

  • Zod validation on every endpoint input
  • No any without comment explaining why
  • Tests in __tests__/ adjacent to source, describe and it blocks 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.details array on failures
  • Validation: authMiddleware plus Zod on every endpoint; schemas use camelCase
  • Routes in packages/api/src/routes/, services in packages/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.ts colocated in __tests__/ adjacent to source
  • Assertion style: use Vitest expect plus toMatchInlineSnapshot for 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

  • PascalCase for exported types and components, camelCase for variables and functions
  • File names: kebab-case for routes, camelCase for utilities
  • State stores: createFooStore pattern, one file per store in src/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 fmt is npx 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.

R1

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - 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.
R2

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation switches between CLAUDE.md at root versus .claude/CLAUDE.md as 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.md plus packages/api/CLAUDE.md to test that multiple project-tier files coexist additively.
R3

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - 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 as use UTC versus use local timezone for payments. Both remain present with subdirectory specialization for that subtree.
R4

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation may frame local file at user tier versus project tier; both are valid positions for a .local variant but exam focuses on project root adjacent usage. - Mutation compares CLAUDE.local.md load order guarantee confusion with settings precedence chain.
R5

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.

instructions.md
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

Memory 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - 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.
R6

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.

terminal
bash
# 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.md

Ordering 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/:

How the same rule gets re-asked
  • - Mutation moves developer from packages/api/ to packages/frontend/ to test sibling exclusion alongside order. - Mutation adds enterprise managed memory before user and project to probe managed-first ordering.
R7

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - 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.
R8

`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.

instructions.md
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
settings.json
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\""
          }
        ]
      }
    ]
  }
}

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.

Wrong answers written against this rule

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\"" } ] } ] } }

How the same rule gets re-asked
  • - Mutation varies enforcement hook event: PreToolUse for blocking versus PostToolUse for auto-fixing, testing lifecycle understanding. - Mutation frames same probabilistic point as CLAUDE.md files are only read on first session startup or maximum effective size of 500 tokens to test load lifecycle misunderstanding.
R9

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.

settings.json
json
// .claude/settings.json deny that deterministically blocks network commands
{
  "permissions": {
    "deny": [
      "Bash(scp *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(nc *)"
    ]
  }
}
settings.json
json
// .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.

Wrong answers written against this rule

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*)" ] } }

How the same rule gets re-asked
  • - Mutation swaps PreToolUse and PostToolUse to test lifecycle understanding: block before versus fix after. - Mutation pairs enterprise managed permissions.deny with personal ~/.claude/CLAUDE.md that permits raw logging to test tier precedence.
R10

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation varies which control is managed: indentation, secrets handling, tool blocking for scp or curl. - Mutation adds managed CLAUDE.md versus managed settings distinction to test that only settings give hard guarantee.
R11

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.

instructions.md
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
instructions.md
markdown
# packages/api/CLAUDE.md selective import
@../../standards/api-conventions.md

Splitting 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.

Wrong answers written against this rule

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

How the same rule gets re-asked
  • - 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.
R12

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation checks whether student verifies import path is relative to containing CLAUDE.md location rather than to project root. - Mutation tests stale session versus missing file as explanation for old requirements after pull.
R13

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation swaps skill import with standards import while preserving relativity test.
R14

`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.

settings.json
json
// .claude/settings.json walk-level exclusion removes vendored tree from discovery
{
  "claudeMdExcludes": [
    "vendor/**",
    "**/__generated__/**"
  ]
}
rule.md
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

Discovery 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.

Wrong answers written against this rule

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

How the same rule gets re-asked
  • - Mutation tests whether student confuses walk exclusion with path-scoped rule negation, both use glob syntax but apply at different stages. - Mutation adds --add-dir added roots to see whether student knows excludes apply to those additional walks as well.
R15

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation swaps --add-dir with --custom-instructions or CLAUDE_CONFIG environment variable fabrication to test which flag actually exists.
R16

`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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation swaps CLAUDE.md staleness with /memory diagnostics to test whether student knows restart versus inspection is the correct remedy.
R17

`/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.

terminal
bash
# 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

Diagnosis 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.

Wrong answers written against this rule

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

How the same rule gets re-asked
  • - Mutation replaces /memory with /status or /config or /inspect as fake diagnostic command to test real command recall. - Mutation frames verification of managed, project, and user tier sourcing to test that /memory displays all three.
R18

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation varies options between /memory, /context, /compact, /clear to probe non-reload understanding.
R19

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation swaps root survival with skill or subdirectory survival to test that only universal always-loaded files are re-read immediately.
R20

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.

rule.md
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
rule.md
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

Many 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.

Wrong answers written against this rule

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

How the same rule gets re-asked
  • - Mutation varies glob axis: directory subtree versus file extension versus type wildcard to test correct tier choice.
R21

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation may frame glob choice as file-type versus directory list to test that type glob /.py is preferred over enumerating kernels/, bindings/* style directory lists when files interleave.
R22

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.

instructions.md
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`
terminal
bash
# Shared file vendored into every repository via submodule or copy-on-clone
shared/
  secure-coding-standards.md

Copy 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.

Wrong answers written against this rule

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

How the same rule gets re-asked
  • - Mutation varies canonical location as shared drive, submodule, or vendored file. Mechanism remains canonical @ import.
R23

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation varies language mix as Python versus TypeScript versus SQL versus Rust, but selective per-package import remains answer.
R24

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.

config.yaml
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
---

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.

Wrong answers written against this rule

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 ---

How the same rule gets re-asked
  • - Mutation swaps translate-vue skill with release-prep or security audit skill. Trigger mechanism stays on-demand.
R25

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.

example.ts
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"
  }
});
result.json
json
// 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.

Wrong answers written against this rule

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"] }

How the same rule gets re-asked
  • - Mutation varies whether SDK ignores all filesystem versus only user scope. Correct answer remains no filesystem by default unless explicitly included.
R26

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.

instructions.md
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}`
.mcp.json
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}"
      }
    }
  }
}

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.

Wrong answers written against this rule

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}" } } } }

How the same rule gets re-asked
  • - Mutation swaps CLAUDE.md secret with .mcp.json token. Both require env expansion pattern.
R27

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - 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.
R28

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/.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - Mutation adds secondary problem of cross-topic bleed where 18% of edits apply Kotlin rules to Swift files. Fix then pairs moving to project scope with path-scoped rule or directory CLAUDE.md axis choice. - Mutation extends to Spark dataset conventions versus universal standards and PR template to test that consultancy multi-client framing still reduces to tier mismatch.
Next.js monorepo that drifts for the new teammate then stops drifting
A production walkthrough with the reasoning chain made explicit.

A four person fintech team ships a Next.js monorepo with web, api, and shared packages whose conventions diverge sharply. The api uses Zod for validation, supertest for HTTP tests, and structured logging, the web app uses Testing Library, vitest browser, and a different mocking convention, shared utilities have only type tests. Putting everything in one root CLAUDE.md makes every package pay the cost of every other package's conventions and dilutes attention on the package the developer is actually editing.

The maintainable structure is a root CLAUDE.md for universal standards such as build commands, branch policy, and code review checklist, plus one CLAUDE.md per package that loads only when Claude reads a file there, with both root and per package files importing shared linting standards via @shared/standards/linting.md so the rule exists once. CLAUDE.local.md at the root carries personal overrides such as a differing test database path. Verification is /context in different directories, root shows project CLAUDE.md plus imported linting, packages/api adds its directory file.

The new team member scenario makes the fault visible. Developer A has months of personal accumulation in ~/.claude/CLAUDE.md including team wide API naming, so on Developer A the model reads both user and project CLAUDE.md and appears consistent. Developer B clones the repo and has no team rules in ~/.claude/CLAUDE.md, so the run reads only the project file and drifts toward defaults. The misdiagnosis is to tell Developer B to copy Developer A's dotfile, the correct fix is to move the team conventions from user level to project level so every clone receives them, then verify with /memory for known locations and /context for what actually loaded.

A final subtlety closes the loop. After /compact summarizes a long session, the project root CLAUDE.md appears intact on resume not because it is privileged but because Claude Code rereads it from disk and reinjects it, while nested CLAUDE.md and path scoped rules with paths frontmatter remain lazy and reappear only when a matching file is read. An instruction that seems to vanish after compact is almost never summarisation but lazy loading not yet retriggered.

Distinctions that decide answers

ThisNot thisHow to tell them apart
CLAUDE.md concatenationsettings.json overrideCLAUDE.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.mdProject CLAUDE.mdUser 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 importAt import directiveThere 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 frontmatterDirectory level CLAUDE.mdPath 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 rulesSkills SKILL.md for on demand workflowsCLAUDE.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

More specific scope wins by shadowing

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.

/memory or /context activates loading

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.

Directory level CLAUDE.md for cross directory file type conventions

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.

Permission rules belong in CLAUDE.md

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.

Subdirectory CLAUDE.md loads eagerly at start

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.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Managed settings and model restriction

The .claude/settings.json stack includes a managed layer that cannot be overridden even by --settings, plus availableModels that restricts callable models from any lower scope, and companyAnnouncements plus disableBundledSkills and skillOverrides.

Configuration
Skill aware memory and canUseTool gating

Agent SDK canUseTool fires per tool call to allow, deny, or modify at runtime, and a PreToolUse hook returning permissionDecision ask surfaces human approval for sensitive operations such as high value refunds.

Best Practices
Schema hint for typo detection

A $schema pointer to https://json.schemastore.org/claude-code-settings.json enables editor autocomplete and flags misspelled keys such as disableBundleSkills before the file reaches the runtime.

The .mdc Configuration File System
Build it
Build a scoped memory repo and prove what loads where
  1. Create .claude/CLAUDE.md at the repo root with three sections, naming conventions such as camelCase variables and PascalCase components, error handling pattern such as wrap async with try catch and return structured error, and review checklist item such as no hardcoded credentials.
  2. Add packages/api/CLAUDE.md with REST conventions for endpoint naming and validation, then create .claude/rules/testing.md with YAML frontmatter paths covering star star slash star dot test dot ts and sibling patterns and three test conventions.
  3. Add an @path import in .claude/CLAUDE.md referencing ./standards/naming.md, create the imported file with two naming rules, and keep the import outside any code span or fenced block so it expands.
  4. Run /context from the repo root and from packages/api and while editing a dot test dot ts file, recording Memory files each time to confirm root plus imported linting at root, plus directory file in api, plus testing rules only when the edited file matches.
  5. Move one convention from project level to ~/.claude/CLAUDE.md in a simulated second user home and verify that a fresh clone without that home does not see the rule, confirming the user versus project sharing boundary.
  6. Add a second team's CLAUDE.md under experimental slash and set claudeMdExcludes to skip it, then verify /context no longer lists it even when walking near that tree.

Verify. Each directory sees the correct concatenated set, path scoped rules appear only for matching files, @path imports expand inline, and the new teammate receives standards from the repo rather than a dotfile copy.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 3.216 min

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 with when_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 to description for 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 $name substitution in the skill body.
  • disable-model-invocation (optional, default false). Set true to 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, default true). Set false to hide the skill from the / menu; Claude can still auto-load it. Use for background knowledge. Note: setting both user-invocable: false and disable-model-invocation: true makes 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 as Bash(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 to allowed-tools; it dominates allowed-tools for any overlapping tool.
  • context (optional). Set to fork to run the skill in an isolated subagent context.
  • agent (optional, default general-purpose). Which subagent type to use when context: fork is set; built-in values include Explore, Plan, general-purpose, or any custom subagent from .claude/agents/.
  • background (optional, default true, requires v2.1.218+). Only applies with context: fork. Set false to wait for the forked subagent's result in the turn that invoked the skill.
  • model (optional, default inherit). Model to use while the skill is active, for the rest of the current turn; accepts the same values as /model or inherit. 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, default bash). Shell for !command dynamic-injection blocks: bash or powershell.
  • 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/, plugin skills/ directories, and .claude/skills/ up the parent chain to the repo root. The loader decides that a directory with SKILL.md is a skill and that a flat .md file in commands/ is a command.
  • Frontmatter parsing: owned by the CLI. Malformed YAML loads the body with empty metadata, so /name still works but no description is available for matching; run with --debug to see the parse error, or claude plugin validate on the skills directory (v2.1.233+) to find unparseable files.
  • Invocation decision (explicit vs automatic): the description is 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: true and user-invocable: false are CLI-enforced gates on that decision.
  • Tool pre-approval and denial: allowed-tools and disallowed-tools are 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 overrides allowed-tools.
  • Subagent fork execution: owned by the CLI plus the agent runtime. context: fork plus agent selects the subagent type (model, tools, permissions); the forked subagent runs in the background by default and applies its edits outside session checkpoints, so /rewind does 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.json permission deny rules and managed settings, not by skill frontmatter. disallowed-tools is 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:

  1. allowed-tools as restriction versus pre-approval. Community and exam-guide posts describe allowed-tools as 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 is disallowed-tools or permission deny rules.
  1. 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-tools is a documented frontmatter field that removes tools from the pool. The exam guide's "live field list" already includes it. Treat disallowed-tools as real and current.
  1. A skills frontmatter list inside CLAUDE.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 are disable-model-invocation in the skill and skillOverrides in settings. Mark this not independently confirmed for any claim that CLAUDE.md carries a skill list.
  1. 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.
  1. Documentation domains. Our lessons cite code.claude.com/docs/en/...; the verified URL list uses docs.claude.com/en/docs/.... Both resolve to the same current docs; the verified list is preferred for citations.
  1. Added-directory skills loading. An --add-dir directory 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 the project setting source is enabled (the default); in --safe-mode none of the three (skills, commands, subagents) load from added directories. By contrast, CLAUDE.md from an added directory is NOT loaded by default and requires CLAUDE_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 for CLAUDE.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.md hierarchy and golden rules (configuration). Task 3.2 tests the boundary between always-loaded CLAUDE.md and on-demand skills; the companion lesson explains the four-level CLAUDE.md chain (global, project, local, directory), the concatenation-not-override rule, the @path import syntax, and the six golden rules. Understanding that CLAUDE.md loads 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 global CLAUDE.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 via SKILL.md (not a separate descriptor) and that most session-management commands are interactive-only while user-invoked skills work in -p mode 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 /compact versus /clear distinction matters: /compact summarizes history to free context and keeps what matters, while /clear starts fresh on a new task and only reloads CLAUDE.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 /plugin command; a skill folder with a .claude-plugin/plugin.json manifest becomes a plugin that can bundle skills alongside agents, hooks, and MCP servers. There is no --install-skill CLI 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 the Skill tool to disable all skills, use Skill(name) or Skill(name *) rules to allow or deny specific skills, and distribute via git (project-local) or the plugin and /plugin system (wider sharing). The skillOverrides setting 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 frontmatter disallowed-tools is 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.

output.txt
text
.claude/skills/review/
  SKILL.md
  checklist.md
config.yaml
yaml
---
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 *)
---
instructions.md
markdown
# 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.

SKILL.md
markdown
# .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.

rule.md
yaml
---
# 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.

SKILL.md
markdown
# .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: $ARGUMENTS

This 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).

config.yaml
yaml
---
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"
---
instructions.md
markdown
# 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.

output.txt
text
apps/web/.claude/skills/deploy/
  SKILL.md
.claude/skills/deploy/
  SKILL.md
my-plugin/skills/deploy/
  SKILL.md
config.yaml
yaml
# 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 *)
---
config.yaml
yaml
# 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.

R1

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R2

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation asks for the entrypoint name specifically (SKILL.md versus index.md versus command.md). Another swaps the example command name. The rule is stable across all of them.
R3

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation uses a different filename (brainstorm.md, deploy.md) in the wrong folder. A mutation asks whether moving it to commands/ fixes it - yes. The rule is invariant to the command name.
R4

.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/.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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/.
R5

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R6

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R7

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation asks the scope of .claude/rules/ (project) versus ~/.claude/rules/ (user). Another asks whether the same uniformity applies to skills - yes.
R8

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R9

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R10

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.

config.yaml
yaml
---
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R11

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R12

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."

config.yaml
yaml
---
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation swaps the required tool set (read-only versus write-only). Another asks whether allowed-tools alone prevents shell execution - exam says yes, live says no (needs disallowed-tools). The rule is stable in exam terms.
R13

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation asks for "the actual security boundary" - answer disallowed-tools plus permission settings. Another frames it as exam-versus-live behavior. Stable.
R14

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.

config.yaml
yaml
---
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."

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the expected argument (version string, module path, migration name). Another asks which field surfaces the label - always argument-hint.
R15

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.

config.yaml
yaml
---
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation asks which field populates the /help tooltip - description. Another asks what discovery reads - also description. Stable.
R16

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation asks about .claude/rules/ loading - also always-on, reinforcing the distinction. Stable.
R17

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R18

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the file type (test files, API files). Another asks whether .claude/rules/ is for skills - no, it is for conventions. Stable.
R19

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation asks what enables auto-invocation - a good description. Another asks how to prevent it - disable-model-invocation: true. Stable.
R20

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).

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the file type pattern. Another asks intent versus path matching. Stable once the field is confirmed.
R21

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation asks which flag disables auto-loading - disable-model-invocation. Another pairs it with explicit-only invocation. Stable.
R22

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.

instructions.md
markdown
# .claude/commands/review-code.md
Review the following code for: security vulnerabilities, performance issues, and
adherence to our coding standards. Focus on: $ARGUMENTS

Commands 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the parameter (diff, file pattern, version). Another asks whether the command needs separate files per value - no, $ARGUMENTS handles it.
R23

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the filename (run-lint.md, generate-tests.md). Another asks about the extension's effect - it is stripped. Stable.
R24

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation swaps which path holds which definition; the skill still wins. Another asks what the commands alias loses on collision - precedence. Stable.
R25

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the skill (/commit, /generate-tests, /analyse). The contradiction persists across all of them. Record as open.
R26

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R27

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."

config.yaml
yaml
---
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/.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the tool set or the verbosity, but the fork-plus-tools pattern persists. Another offers commands/ placement - acceptable but less featured than skills/. Stable.
R28

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.

SKILL.md
markdown
---
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation changes the field being tested (description versus argument-hint). Another asks whether .claude/commands/ supports allowed-tools - yes. Stable.
R29

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation swaps the languages (React to Vue, or any pair). Another removes forking and tests raw CLAUDE.md placement - still wrong for on-demand. Stable.
R30

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
Team /review plus personal /brainstorm with correct isolation
A production walkthrough with the reasoning chain made explicit.

A team needs two reusable commands that must have opposite sharing and isolation behavior. The first is /review, a code review workflow with error handling, test coverage for new functions, API naming, and hardcoded credential checks that every teammate must be able to run. The second is /brainstorm, a personal workflow that explores a feature area, lists files, builds dependency graphs, and produces output too verbose for the main conversation. A third is a security audit that must never auto fire but must be available on demand with OWASP guidance.

For /review the correct file is .claude/commands/review.md or as a skill .claude/skills/review/SKILL.md under the repo so the file rides via git. The body is a checklist that runs against the working diff, and teammates get the command on clone. Placing this file in ~/.claude/commands would make it personal and invisible to the team, which is the exam fault for the team command location scenario. Dropping a flat review.md directly into .claude/skills would create no command at all because skills require a directory containing SKILL.md.

For /brainstorm the correct file is ~/.claude/skills/brainstorm/SKILL.md with frontmatter context fork so the workflow runs as an isolated subagent. The verbose listings and excerpts stay contained in the fork and the main conversation receives only the summary, keeping the main window clean for implementation. Without the fork flag the main window fills with exploration noise and subsequent turns degrade, which is the canonical trap for verbose output clutters the main conversation. The skill also lists allowed-tools for Read, Grep, and Glob so those reads run without per use approval while the skill is active, with other tools still governed by baseline permissions.

For the security audit the skill adds disable-model-invocation true so it never auto invokes and requires an explicit /security-review. The body carries OWASP guidance and reporting templates, and permission rules can gate it further with Skill(security-review). A common wrong answer is to place always on security guidance in a skill that never gets invoked, which makes it invisible, or to put a noisy occasional workflow in CLAUDE.md where it costs tokens on every session. The correct split is universal standards in CLAUDE.md or path scoped rules and task procedures in skills with explicit only when the workflow is sensitive.

Distinctions that decide answers

ThisNot thisHow to tell them apart
.claude/commands/name.md.claude/skills/name/SKILL.mdBoth 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 workflowsCLAUDE.md always on standardsSkills 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 skillInline skillForked 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 trueallowed-tools onlyThe 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/skillsUser ~/.claude/skillsProject 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 commandBundled skill commandFixed 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

Flat file directly in .claude/skills

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.

Team command placed in user scope

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.

Always on guidance placed in a skill

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.

Skipping context fork for a verbose skill

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.

allowed-tools as a strict security boundary

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.

Expecting every built in to work in -p

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.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Reference versus task content in the same frontmatter

Reference skills add knowledge inline while task skills give step by step instructions and typically set disable-model-invocation true, with context fork for noisy reference aggregation versus explicit only for destructive tasks like deploy.

Skill Management in Claude Code
Plugin and marketplace distribution

A skill folder with .claude-plugin/plugin.json bundles skills alongside agents, hooks, and MCP servers, and marketplaces sourced from GitHub, URL hosted marketplace.json, npm package, or local path are enabled via extraKnownMarketplaces and enabledPlugins in settings.json with layered precedence.

MCP Integration
Context budget between memory and skills

CLAUDE.md loads on every session and consumes budget always, while skill body loads only when invoked, so long reference material should live in skills rather than memory to avoid taxing every turn until needed.

Configuration
Build it
Ship /review for the team and /brainstorm for yourself with correct isolation
  1. Create .claude/commands/review.md containing a code review checklist with error handling, test coverage, API naming, and credential checks, and verify /review appears after a clone and triggers the checklist.
  2. Create ~/.claude/skills/brainstorm/SKILL.md with frontmatter context fork plus allowed-tools listing Read, Grep, and Glob and argument-hint for the area to explore, and mark whether model invocation is desired for this noisy workflow.
  3. Add a security audit skill with disable-model-invocation true and allowed-tools for reading, confirming it never auto invokes but runs on explicit /security-audit with promptless reads.
  4. Verify sharing boundary, /review works in any clone of the repo while /brainstorm appears only in your home scoped session, and that the brainstorm verbose listings are not visible in the main conversation history after invocation.
  5. Convert one .claude/commands file to .claude/skills/name/SKILL.md and verify both create the same /name with skill precedence when both exist.
  6. Invoke /brainstorm via claude -p with headless flags to confirm custom skills work in print mode while interactive only commands such as /clear do not apply the same way headlessly.

Verify. Team holds a shared /review, personal noisy work stays fork isolated, sensitive audits require explicit invocation, and the same commands behave correctly in interactive and headless shells.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 3.314 min

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:

  • */.ts matches all TypeScript files in any directory.
  • src/*/ matches all files under the src/ directory at any depth.
  • *.md matches markdown files in the project root.
  • src/components/*.tsx matches 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.

GuaranteeOwnerWhat the owner controlsFailure mode when another layer is used
Whether a rule loads for this fileCLI runtime that evaluates paths globs against read pathsPattern expansion, budget enforcement (1,000 patterns, 4 MiB), bracket validation, symlink-aware matching as of 2.1.198Prose headers in CLAUDE.md cannot prevent loading, the text is already in context and the gating is probabilistic
Whether a rule is available at allFile system and settingSources selectionDiscovery of .md files under .claude/rules/ recursively, respecting project exclusion after 2.1.211, and honoring symlinksPlacing a rule outside .claude/rules/ or excluding project without realizing it hides the rule
Whether instructions are followedModel interpreting contextAttention over the concatenated instruction set, with last-word ordering for CLAUDE.md hierarchy but no override for conflicting rulesContradictory rules cause arbitrary selection, not deterministic override
Whether behavior is enforced regardless of interpretationHooks and settings.json permission layersPreToolUse and related hook events that can block a tool call outside model judgmentWriting a prohibition only in CLAUDE.md leaves it as guidance, not enforcement
Whether irrelevant instructions are present in contextConfiguration composition (@path imports and claudeMdExcludes)Imports load eagerly at launch, exclusions skip by glob against absolute paths, with managed policy unable to be excludedUsing @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 the project exclusion in settingSources in version 2.1.211, before which they loaded even when project was excluded.
  • Brace-heavy paths values 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 /init flow gated by CLAUDE_CODE_NEW_INIT=1 and the one-time /import for 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:

instructions.md
markdown
---
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:

rule.md
yaml
---
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:

output.txt
text
repo/
  CLAUDE.md
  .claude/
    rules/
      testing.md
      api-conventions.md
      terraform.md
      frontend.md

File CLAUDE.md becomes a short index for universal standards and imports always-loaded shared material:

# Project Standards

Architecture

  • Services in src/api/, components in src/components/.
  • State in Zustand stores, not component useState except for ephemeral UI.

Shared Reference

  • Shared linting rules @../shared/linting-rules.md

Commands

  • Build npm run build, test npm test, lint npm 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:

rule.md
yaml
---
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:

rule.md
yaml
---
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.

output.txt
text
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.tsx

Each CLAUDE.md in this tree would need to carry a copy of the same testing conventions:

instructions.md
markdown
# 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:

rule.md
yaml
---
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.

terminal
bash
mkdir -p .claude/rules src/components src/api terraform
cat .claude/rules/testing.md

Observable 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.

instructions.md
markdown
---
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.

instructions.md
markdown
---
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.

rule.md
yaml
---
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.

terminal
bash
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);
  });
});
EOF

In a running Claude Code session, open src/components/Button.test.tsx for reading or editing, then run the diagnostic:

terminal
bash
# Inside the Claude Code session
/context

Observable 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.

terminal
bash
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: {} });
}
EOF

Open 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 describe and it blocks 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 environment and team.

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.

terminal
bash
cat > .claude/rules/ungated.md <<'EOF'
# Unscoped Conventions

- This file has no paths gate and therefore loads for every session.
EOF

Run /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:

terminal
bash
cat > .claude/rules/ungated.md <<'EOF'
---
paths:
  - "**/*.md"
---

# Scoped Conventions

- This file now loads only for markdown files.
EOF

Run /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.

terminal
bash
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.
EOF

Run /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: or path: instead of paths: leaves the file unconditional, so it appears in every /context where it should have been absent. The forensics file flags this as the field-name trap where community globs spelling is mistaken for canonical.
  • Directory-bound under-match: writing terraform/ instead of terraform// misses terraform/modules/networking/vpc.tf because * does not cross separators, while is required for depth.
  • Assuming negation: writing !/__generated__/ inside paths and expecting exclusion, when the documentation provides claudeMdExcludes in settings.json as the exclusion primitive and does not document ! for paths not independently confirmed.
  • Import confusion: adding @./standards/testing.md inside CLAUDE.md to simulate conditional loading, when imports expand eagerly at launch and therefore keep testing conventions always present.
  • Source exclusion: excluding project from the SDK settingSources and concluding that paths evaluation is broken, when the rule was never loaded because its tier was excluded.

Terraform Conventions: Quick reference table for the build

StepFileFrontmatter pathsExpected 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/
4Any neutral file such as src/utils/format.tsNone of the above matchNo rule file appears, only root CLAUDE.md
5Overlapping file such as src/api/auth.test.tsMatches both testing and API listsBoth 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.

R1

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:

rule.md
yaml
---
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`
output.txt
text
repo/
  CLAUDE.md
  .claude/
    rules/
      terraform.md
      testing.md
      api-conventions.md
  terraform/
    networking/
      vpc.tf
  src/
    components/
      Button.tsx

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Changing /.test.ts to src/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.
R2

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.

rule.md
yaml
---
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 implementation

Path 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Narrowing /.test. to /.test.ts drops .test.tsx files from coverage, which changes the answer for React component tests. Expanding src/api// to /api/*/ widens from a rooted prefix to any subtree named api, covering services/billing/api/handlers.ts as well.
R3

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.

output.txt
text
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.tsx

Conventions 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/.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Replacing /.tf with terraform//.tf narrows from every .tf file in the repo to only those under terraform/, which misses services/billing/infra/main.tf. Replacing terraform/*/ with terraform/* narrows to the top level only and misses terraform/modules/networking/vpc.tf.
R4

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.

rule.md
yaml
---
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
rule.md
yaml
---
paths: ["**/*.py"]
---
# Python Conventions

- Follow PEP 8, enforce with `ruff`
- Type hints required on all public functions
- Never use bare `except`
rule.md
yaml
---
paths: ["**/*.sql"]
---
# SQL Conventions

- Use `snake_case` for identifiers
- Every migration must have a rollback section
- Never `SELECT *` in production code

When 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/.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Switching /.py to services//.py narrows from every Python file to only those under services/, missing tools/scripts/migrate.py. Switching /.tsx to /.ts widens to include plain TypeScript files and drops the JSX-specific convention from non-JSX files.
R5

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.

rule.md
yaml
---
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Moving a database runbook from root CLAUDE.md into .claude/rules/db.md with paths: ["db/migrations//"] changes the cost from always-loaded to file-triggered. Moving a universal baseline out of root CLAUDE.md into .claude/rules/security.md with an enumerated list such as paths: ["/.py", "/.go", "/.ts"] changes it from universal to gated and introduces holes.
R6

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.

output.txt
text
repo/
  CLAUDE.md               # 800 lines: Terraform + React + API + testing + deployment
  .claude/
    rules/
  terraform/
  src/
    components/
    api/
instructions.md
markdown
## 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 components

Root 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Moving Terraform conventions from root CLAUDE.md into .claude/rules/terraform.md with paths: ["*/.tf"] changes them from always-loaded to file-conditional. Splitting root CLAUDE.md into @import modules without adding paths gates keeps them always-loaded and changes only maintainability.
R7

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.

output.txt
text
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 conventions

Directory 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Replacing a type glob /.test. with a directory glob features/ changes from file-type matching to subtree matching, which contaminates non-test files under features/ and fails to cover test files outside it. Replacing per-directory CLAUDE.md copies with a single .claude/rules/testing.md with paths: ["*/.test.*"] collapses many files to one and makes coverage future-proof.
R8

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/.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Changing a root CLAUDE.md section into a .claude/rules/ file with paths: ["*/.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.
R9

`@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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Adding @import to 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.
R10

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.

rule.md
yaml
---
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

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Adding paths: ["/.test.ts", "/.test.tsx", "*/.spec.ts"] to an always-loaded testing.md makes 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.
R11

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.

output.txt
text
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.ts
output.txt
text
repo/
  .claude/
    rules/
      testing.md         # single file with paths: ["**/*.test.ts", "**/*.test.tsx"]
  src/
    components/
      Button.test.tsx
    api/
      auth.test.ts
    utils/
      format.test.ts

Duplication 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Changing /.test. to /.test.ts drops .test.tsx coverage and silently excludes React component tests. Changing /migrations//.py to /migrations//* widens from Python migration files to every file under migration directories.
R12

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.

rule.md
yaml
---
paths: ["ios/**"]
---
# Swift Conventions

- Use value types for models
- Prefer SwiftUI patterns for views
- Enforce strict access control

Scoping 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.

Wrong answers written against this rule

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..

How the same rule gets re-asked
  • Switching paths: ["/.swift"] to paths: ["ios/"] changes from any Swift file anywhere to any file under ios/ and widens to include non-Swift files inside ios/. Switching paths: ["kernels/", "bindings/"] to paths: ["/.rs"] changes from an enumerated set of known locations to every Rust file including future locations.
R13

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.

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Moving a universal baseline from root CLAUDE.md into .claude/rules/security.md with any enumerated paths list changes it from guaranteed to gated and introduces holes. Changing paths: ["*/"] in that rule back to root CLAUDE.md restores the guarantee and makes intent explicit.
R14

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Expanding paths: ["/.py", "/.go"] to include /.ts extends coverage but keeps the structure enumerated and still gated. Moving the baseline from .claude/rules/security.md with paths: ["/"] to root CLAUDE.md changes from a path-matched universal simulation to a truly always-loaded baseline.
R15

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.

rule.md
yaml
---
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 edits

Generated 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Replacing paths: ["src//.ts", "src//.tsx"] with paths: ["/.ts", "/.tsx", "!/__generated__/", "!vendor/"] widens from package-root src/ 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.
R16

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.

rule.md
yaml
---
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Switching paths: ["/.tf"] to paths: ["modules/", "envs/staging/"] narrows from every Terraform file to only those under known prefixes and introduces a maintenance tail. Switching paths: ["src/mobile//"] to paths: ["*/.tsx"] widens from one platform directory to every TSX file and contaminates web components outside src/mobile/.
R17

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:

output.txt
text
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.md

Sharing 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Moving ~/.claude/CLAUDE.md text into project-level CLAUDE.md committed at the repo root changes from personal reach to team reach. Moving it instead into .claude/rules/coding-standards.md with paths: ["/.c", "/.h"] also moves to team reach but adds a file-type gate.
R18

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Converting CLAUDE.md always-loaded sections about PR, deploy, and migration into Skills while keeping coding and testing standards in CLAUDE.md changes from always-loaded to on-demand for workflow guidance. Moving the same migration checklist from a Skill into .claude/rules/ with paths: ["/migrations//*"] changes from intent-triggered to file-triggered.
R19

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.

config.yaml
yaml
---
name: incident-response
context: fork
---
# Incident Response Runbook

- Replay ordering, checkpoint recovery, backfill validation
- Multi-step diagnostic output is expected and verbose

Path-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.

Wrong answers written against this rule

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 */.

How the same rule gets re-asked
  • Moving the runbook from root CLAUDE.md into a Skill with context: fork removes it from routine context and isolates its output. Changing the Skill to omit context: fork keeps it out of routine context but lets verbose output fill the main session.
R20

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.

rule.md
yaml
---
paths: ["**/*.test.ts", "**/*.test.tsx"]
---
# Testing Conventions

- Use `describe` and `it` with sentence-style names
- Assert behavior, not implementation
rule.md
yaml
---
paths: ["src/api/**/*"]
---
# API Conventions

- All endpoints return `{data, error, metadata}`
- Validate with `zod` at the handler boundary

Whole-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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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/*/ to src/api/*.ts, reduces the overlap to one level and hides deeper test files.
R21

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:

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Splitting CLAUDE.md covering TypeScript, testing, API, and deployment into four .claude/rules/ files changes from one mixed file to four owned files. Merging testing.md and api-conventions.md back into one file reduces ownership clarity and couples two teams' reviews.
R22

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.

Wrong answers written against this rule

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/.

How the same rule gets re-asked
  • Adding @import to 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.md with paths: ["*/.test.ts"] changes that section from always-loaded to conditional.
R23

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Changing paths: ["terraform//"] to paths: ["infra/terraform//"] fixes a mismatch when Terraform is actually under infra/terraform/. Changing paths: ["/.md"] to paths: ["docs/portal//.md"] narrows from every Markdown file to only portal documentation and would drop other Markdown files intentionally.
R24

`.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.

rule.md
yaml
---
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Switching alwaysApply: true to alwaysApply: false plus globs: ["/.tf"] changes from always-on to file-type gated. Switching paths: ["/.tf"] to globs: ["*/.tf"] changes only the field spelling while preserving the gating intent, subject to tooling's canonical name.
R25

`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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Replacing Glob with Grep for */.test.ts switches from path match to content search and produces false positives and negatives. Replacing Grep with Glob for fact_revenue_daily switches from content search to filename search and misses nearly every real reference.
R26

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.

output.txt
text
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.py

Subtree 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Changing ios/CLAUDE.md to paths: ["/*.swift"] widens from subtree to whole tree by type. Changing paths: ["ios/"] to ios/CLAUDE.md moves from a centralized conditional rule to a co-located subtree file while preserving containment.
R27

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.

output.txt
text
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 universal

Rewording 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.

Wrong answers written against this 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.

How the same rule gets re-asked
  • Splitting CLAUDE.md via @import without adding any paths gates keeps every section always loaded and improves only maintainability. Moving topic-scoped sections into .claude/rules/ with paths: ["api/"] changes from always-loaded to conditional and reduces tokens for unrelated sessions.
R28

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.

instructions.md
markdown
# CLAUDE.md

- Project notes and architecture decisions

@./shared/secure-coding-standards.md

Reference 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Replacing inline standards in each repository's CLAUDE.md with @./shared/secure-coding-standards.md converts 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.md with a path gate reintroduces holes for non-matching types.
Three convention sets across tests, APIs, and Terraform with token proof
A production walkthrough with the reasoning chain made explicit.

A team holds three convention categories with divergent scopes. Test conventions for naming, assertions, and mocking should apply to star star slash star dot test dot ts and sibling spec patterns wherever they live, API conventions for response shape { data, error, metadata } and Zod validation should apply to src/api, star star slash routes, and dot controller dot ts files, and infrastructure conventions for remote state backend and workspace separation should apply to terraform slash star star and star star slash star dot tf. File type ranges overlap in the filesystem tree but never in the file types touched during a single edit.

The wrong solutions illustrate the cost model. All three sets in root CLAUDE.md load for every session regardless of whether the developer edits a test, an API handler, or a Terraform module, paying around 800 tokens even when only one set is relevant and crowding the middle of context. Directory level CLAUDE.md would need manual placement in every directory containing tests for the first set, which across 50 plus directories is both drift prone and token heavy per directory even when untouched. Skills with paths could auto trigger as workflows, but the requirement is always on background guidance, not an on demand checklist, so rules are the correct always on conditional memory.

The path scoped fix is three rule files. testing.md with paths star star slash star dot test dot ts, star star slash star dot test dot tsx, star star slash star dot spec dot ts, and star star slash star dot spec dot tsx carries describe it blocks, happy path plus error case per file, factory functions for test data, module boundary mocking, and assert behaviour not implementation. api-conventions.md with paths src/api slash star star, star star slash routes slash star star, and star star slash star dot controller dot ts carries endpoint response shape, request validation at handler boundary, request identifier on errors, and explicit rate limit configuration. terraform.md with paths terraform slash star star, star star slash star dot tf, and infrastructure slash star star carries remote backend requirement, workspace separation, and module versioning with changelog.

The runtime proof is via /context per edit. Starting in the repo root and opening a dot test dot ts file under src/utils, the walk up contributes universal standards while testing.md matches and loads and the other two do not. Switching to a Terraform module under terraform slash modules slash vpc slash main dot tf, terraform.md matches and loads while earlier rules persist but no new test or API guidance is added. With all three in root the loaded set is always 800 tokens, with path scoped rules a Terraform only edit is about 500 tokens for universal 300 plus Terraform 200, saving 300 tokens every turn Terraform is edited and compounding across the session.

Distinctions that decide answers

ThisNot thisHow to tell them apart
.claude/rules with pathsDirectory level CLAUDE.mdRules 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 pathsRoot CLAUDE.mdRules 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 pathsSkills SKILL.md with pathsBoth 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 tssrc slash star starFile 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.
claudeMdExcludesAllow ask deny permissionsclaudeMdExcludes 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

Directory CLAUDE.md for cross directory test conventions

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.

Root CLAUDE.md for file type specific conventions

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.

Skills instead of rules for always on conventions

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.

Forgetting claudeMdExcludes in a noisy monorepo

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.

Directory glob when file type glob is more precise

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.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Multi pattern rule that spans four test extensions

Paths can combine star star slash star dot test dot ts, star star slash star dot test dot tsx, star star slash star dot spec dot ts, and star star slash star dot spec dot tsx in a single rule so one file covers every relevant test file without per extension rules.

Configuration
Unlimited body size for conditional rules

Unlike always loaded CLAUDE.md where brevity matters on every turn, a path scoped rule can be as long as needed because it contributes zero tokens until a matching file is read.

The .mdc Configuration File System
Standardized announcement and model gating

companyAnnouncements for org wide banner text and availableModels to restrict callable models from any lower scope are settings.json companions to path scoped memory, tested alongside claudeMdExcludes as layered precedence.

Best Practices
Build it
Prove conditional loading cuts tokens versus root memory
  1. Create .claude/rules/testing.md with paths for star star slash star dot test dot ts, star star slash star dot test dot tsx, and star star slash star dot spec dot ts plus three test conventions, and .claude/rules/terraform.md with paths terraform slash star star and star star slash star dot tf plus two infra conventions.
  2. Also create .claude/rules/api-conventions.md with paths src slash api slash star star and star star slash routes slash star star plus three API conventions, keeping universal guidance in root .claude/CLAUDE.md only.
  3. From the repo root edit a dot test dot ts file under src/utils, run /context, and record Memory files to show testing rules loaded while API and Terraform rules did not, then edit a Terraform module under terraform and repeat to show the swap.
  4. Move the same conventions into root CLAUDE.md as a second branch and compare loaded memory via /context while editing a utility with no conventions, showing the unified file always contributes while the scoped run contributes only universal plus matching rule.
  5. Demonstrate the directory level failure by sketching how many CLAUDE.md files would be needed to cover test types across many directories versus the single rule file with star star globs.
  6. Set claudeMdExcludes for an experimental subtree, verify /context no longer lists memory under it, and document the saved tokens versus the unconditional baseline.

Verify. The edited file sees only the rule its glob matches, the token count is measurably lower than the unified root baseline, and the same coverage that would need many directory files is handled by one rule per file type.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 3.416 min

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 is default, and the CLI accepts the manual alias (the manual alias requires Claude Code v2.1.200 or later).
  • acceptEdits - Reads, file edits, and common filesystem commands such as mkdir, touch, mv, cp run 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 interactive Shift+Tab cycle; 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 via permissions.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:

  1. CLI flag at launch: claude --permission-mode plan "<prompt>". This sets the starting mode for the whole session.
  2. Live key chord in an interactive session: press Shift+Tab to cycle through the modes. From default, the first press goes to acceptEdits and the second press goes to plan; the cycle then returns to default. On Windows when the Node or Bun runtime does not enable VT input mode, Alt+M performs the same cycle.
  3. 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.

terminal
bash
# 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 valueWhat runs without a promptBest forLaunch flag
default (Manual)Reads onlyReviewing every action, sensitive workclaude --permission-mode default
acceptEditsReads, file edits, common filesystem commandsIterating on code you reviewclaude --permission-mode acceptEdits
planReads (edits blocked until approved)Exploring before changingclaude --permission-mode plan
autoMost actions, with classifier reviewLong tasks, less prompt fatigueclaude --permission-mode auto
dontAskOnly pre-approved toolsLocked-down CI and scriptsclaude --permission-mode dontAsk
bypassPermissionsEverything (no checks)Isolated containers or VMs onlyclaude --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:

terminal
bash
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 only

Each 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.

request.json
json
{
  "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.

settings.json
json
{
  "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.

terminal
bash
# 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.

terminal
bash
# 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:*)"
done

The 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:

  1. Built-in tool with a parenthesized pattern: Tool(pattern). For Read, Grep, Glob, Edit, and Write, the pattern is a glob over paths, for example Read(/), Edit(.claude/), Write(payments/). For Bash, the pattern matches a command; prefix matching uses a space before the so Bash(git diff ) allows any command starting with git diff, while Bash(git diff) (no space) would also match git diff-index, which is usually not intended. Documented examples include Bash(npm test), Bash(git push *), and Bash(git commit *).
  2. MCP tool with a mcp__server__tool name: the wildcard tool name is only valid after the literal mcp__<server>__ prefix. A bare wildcard tool name does not match MCP tools; to allow all tools of a server you write mcp__server__*.
  3. Skill rules: Skill(name) for an exact match and Skill(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 permissionMode setting, the Shift+Tab cycle, the --permission-mode flag, the --allowedTools flag, and the -p non-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/deny evaluation 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.json permission rules are engine-enforced; CLAUDE.md instructions 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 --agents flag or the Agent SDK agents parameter, 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. The manual alias for default requires Claude Code v2.1.200 or later. Older material may call it "default mode" without the Manual label; both refer to the same default value.
  • auto mode 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 is auto on 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 assume auto is the default everywhere.
  • dontAsk is 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 to dontAsk plus permissions.deny, not to plan mode.
  • acceptEdits auto-approves file edits and common filesystem commands. Earlier guidance sometimes described a narrower "edit approval" behaviour; the current rule includes mkdir, touch, mv, cp and similar.
  • bypassPermissions is the current name for the skip-all-checks mode; --dangerously-skip-permissions is equivalent. It can be blocked org-wide via permissions.disableBypassPermissionsMode.
  • The plan value itself is stable across these releases; the entry and exit mechanics (flag, Shift+Tab cycle, mode switch) are consistent. The unstable parts are the shorthand claude --plan and the /plan and /approve command spellings, which are not independently confirmed against 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 -p disables safety checks. Documentation position: -p is non-interactive and skips the interactive approval gate and the /resume slash command, but permissions, deny rules, and engine-enforced boundaries still apply. Only --dangerously-skip-permissions skips 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 --allowedTools pre-approval and permissions.deny, often with dontAsk as the baseline. Answer with the documentation position.
  • Community claim: there is an /approve command that executes the plan in place. Documentation position: no /approve command is documented; approval is the permission-mode switch that grants write access, and execution is a separate phase. Mark /approve as not independently confirmed and 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.md rule such as "never run rm -rf" is enough to block destructive commands. Documentation position: that is advisory and can be overridden; permissions.deny is 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), the allow/ask/deny rule 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: -p for non-interactive runs, --allowedTools for pre-approval, --permission-mode as a baseline, --output-format json for 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 of dontAsk plus permissions.deny.
  • claude-code-fork-session (claude-code domain). Documents /fork as 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 a plan_mode: true frontmatter 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 without plan_mode follows the session mode. The lesson also covers allowed-tools grants in frontmatter, which are engine-enforced during the invoking turn.
  • configuration, best-practices, mcp-integration, claude-code-print-pref, and claude-code-mdc-config (claude-code domain). Adjacent lessons that round out the permission and execution story: configuration covers settings.json scope and precedence; best-practices covers review discipline; mcp-integration covers how MCP tools enter the permission system as mcp__server__tool; claude-code-print-pref covers the -p/--print non-interactive form; claude-code-mdc-config covers 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 SDK agents parameter, the Agent tool, and MCP transport wiring. A candidate who works at the SDK level should know that subagents there run in fresh isolated contexts and that Agent must be in allowedTools for 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.

terminal
bash
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.

ScenarioSignalModeWhy
Monolith to microservice splitArchitectural, many files, competing approachesplanStructural choices have cross-file consequences
Library or framework migration across many filesUniform conversion pattern neededplan then executeSettling one pattern prevents drift
Single-function bug with clear stack tracePrecise location, known causedirectNo exploration or design needed
Fully specified security fixKnown, isolated, single linedirectUrgency and domain do not force plan
Documentation or terminology refresh across servicesUniform voice and termsplanA glossary and template prevent drift
CI or headless automated reviewNo human in loopdirect with pre-approved toolsMode choice is explicit; safety via permissions
Verbose cross-package discoveryUnknown structure, noisy outputplan with Explore subagentIsolation keeps main context clean
Strict config transformationExact syntax requireddirect with few-shot examplesFormat 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 allowedTools or permissions.deny are 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:

  1. Pick a real refactoring in a repository, for example moving a module into a new package.
  2. Launch claude --permission-mode plan "<refactor description>".
  3. Ask the agent to identify dependencies, list approaches with tradeoffs, and recommend a strategy.
  4. Inspect the working tree with git status and git 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:

  1. Reproduce a clear, localized bug with a known stack trace pointing at one function.
  2. Launch claude --permission-mode default "<one-line fix description>".
  3. 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:

  1. Choose a library migration touching many files, for example a logging or HTTP client swap.
  2. In plan mode, have the agent list every importing file, map API differences, and design one migration pattern.
  3. Leave plan mode via the Shift+Tab cycle or mode indicator.
  4. In a writable session, apply the pattern file by file in the planned order.
  5. Run git diff --stat and 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:

  1. Pick a verbose discovery task, for example tracing a deprecated util across packages.
  2. Delegate it to the built-in Explore agent or to a custom read-only subagent (see Example 3).
  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:

  1. Write down the ambiguity checklist: is the change specific, is the location known, is the approach clear.
  2. 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.
  3. 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:

  1. Add Read(.env) and Bash(rm -rf *) to permissions.deny in .claude/settings.json.
  2. In the same session, add a CLAUDE.md line "never read .env".
  3. Attempt to read .env and attempt rm -rf on 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.

R1

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.

terminal
bash
# 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 form

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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 /approve that transitions to a normal session.
R2

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.

terminal
bash
# After reviewing the proposal inside plan mode:
/approve        # exits plan mode, the agent begins applying the described edits

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R3

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.

output.txt
text
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 mode

Plan 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R4

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.

output.txt
text
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 dominates

Choosing 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R5

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.

output.txt
text
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 seam

Architectural 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R6

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.

output.txt
text
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 began

A 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R7

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.

terminal
bash
# 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 subagent

Exploration 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".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R8

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.

output.txt
text
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 analyse

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R9

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.

terminal
bash
# 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".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R10

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.

output.txt
text
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 files

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R11

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".

terminal
bash
# 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 permitted

A 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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 /plan in plan mode is the review surface.
R12

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.

output.txt
text
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 window

Model 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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 --continue passing summaries between batch calls"; the fix is that this addresses a different problem (async batching) and is not the context-isolation mechanism.
R13

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.

output.txt
text
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".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R14

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".

output.txt
text
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 B

Direct 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R15

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.

output.txt
text
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 benefit

Plan 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".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R16

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."

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R17

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.

output.txt
text
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 plan

Mode 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)".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R18

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.

output.txt
text
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."

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R19

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."

terminal
bash
# 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 agent

Automatic 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R20

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.

output.txt
text
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 surface

Applying 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R21

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.

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R22

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".

output.txt
text
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 tools

Large 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".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R23

`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.

terminal
bash
# 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 docs

Unlike 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."

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation offers allowedTools to restrict to read tools plus Write scoped to the module; the fix is that scoping the write path is what enforces the boundary.
R24

`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.

settings.json
json
{
  "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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation offers "store keys in a separate .env and add a CLAUDE.md rule"; the fix is that the deny list is the deterministic control, with env-var extraction as the hygiene step.
R25

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.

config.yaml
yaml
---
description: Propose a refactor of the auth module
plan_mode: true
---
# command body: Claude will propose a plan first, then await approval

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation pairs this with claude --plan so the command proposes a plan; the fix is that the inheritance comes from the session flag, not the command.
R26

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".

terminal
bash
# 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation offers headless -p to "prevent interactive file edits until re-enabled"; the fix is that -p is non-interactive by design, not a toggle for interactivity.
R27

`--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.

terminal
bash
# 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 batch

Unattended 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".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation offers repeating a prompt with /loop; the fix is that /loop stays in one session and lacks scriptability, so the fan-out pattern prefers per-file claude -p.
R28

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.

.mcp.json
json
{
  "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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R29

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.

request.json
json
{
  "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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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).
R30

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".

output.txt
text
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."

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R31

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".

output.txt
text
Forking is for: comparing two divergent implementations from a shared baseline
Forking is NOT for: a single obvious fix, discovery isolation, or mode selection

A 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).

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R32

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.

output.txt
text
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".

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation offers a general consistency instruction in CLAUDE.md; the fix is that concrete examples, not general instructions, drive syntactic consistency.
Three tasks with different correct modes on the same team
A production walkthrough with the reasoning chain made explicit.

Task one is to restructure a monolith into microservices. The stem lists multi file impact, service boundary design, multiple valid integration architectures with differing infrastructure requirements, and unknown module dependencies that require exploration before any change. The correct mode is plan immediately because complexity is stated in the requirements, not a hidden surprise. During plan the team fans out Explore subagents to trace dependencies in parallel and produces a boundary proposal with tradeoffs, and the main conversation receives only the summary while the verbose discovery stays isolated.

Task two is a null pointer exception in a single function with a clear stack trace, known cause, and a one line null check as the obvious fix. The correct mode is direct execution, the fix is applied, the test passes, and there is no design decision to justify planning. The wrong answer here is to enter plan mode because the bug mentions several files in the trace, the discriminator is not file count but whether a design choice exists, and here none does.

Task three is migration from an old logging library to a new one across 30 files with API differences per importer. The correct mode is plan then execute, the plan identifies every importer, maps the surface difference, designs a per file pattern and notes edge cases such as contextual formatting, and the execute phase applies that pattern consistently so no file diverges. A naive direct pass would have applied the migration inconsistently, which is the failure the exam targets. Starting direct and switching only when files diverge incurs rework that upfront planning would have avoided.

A variant the exam probes is the deferred switch trap, starting direct on a complex migration and moving to plan only when the first few files reveal unexpected coupling. The upfront plan would have discovered that coupling during dependency tracing and produced a different ordering or boundary choice, so the late switch costs at least the work already applied that must be redone. The exam marks the upfront plan as the cost correct answer because rework exceeds planning overhead.

Distinctions that decide answers

ThisNot thisHow to tell them apart
Plan modeDirect executionPlan 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 alonePlan then execute hybridPlan 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 subagentMain agent explorationExplore 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-reviewPlan 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 ultraLocal /code-reviewUltra 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

Default to direct for multi file architectural work

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.

Plan for a single file fix with a clear stack trace

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.

Skip the plan then execute hybrid

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.

Defer plan until complexity shows up

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.

Skip Explore subagent for verbose discovery

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.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Pre ship verification as completion of the mode split

The /diff plus /code-review plus /security-review trio with --fix and cloud ultra verifies what execution produced after plan informed the design, which the exam presents as the close the loop companion to mode choice.

Claude Code Slash Commands
Headless plan generation in CI

--permission-mode plan with -p is the headless analogue of interactive plan mode that generates risk reports or plans in CI without edits, with different iteration properties from interactive plan.

CI/CD Integration: --print and Non-Interactive Flags
Worktree decomposition for large migrations

/batch decomposes a large codebase spanning change into independent worktree units, which composes with plan then execute by parallelising the per file apply after the design is set.

Workflow Patterns
Build it
Classify three tasks and run the hybrid on the migration
  1. Select one complex multi file task with architectural choice, one single file bug with a clear stack trace, and one 30 file library migration where many files import the old library with API differences, and label each with ambiguous versus well understood at arrival.
  2. Run the complex task in plan mode with one or two Explore subagents, record the identified dependencies, evaluated approaches with tradeoffs, and recommendation, confirming no files were modified during planning and that verbose listings stayed in the subagent.
  3. Run the single file bug with direct execution, measure time from prompt to fix, and contrast with a staged plan mode attempt to observe planning overhead with no design change.
  4. For the migration run the hybrid, phase one as plan to collect every importer and design the per file pattern with edge handling, phase two as direct execution file by file with consistent verification, and compare against a single pass direct attempt for divergence per file.
  5. Produce a one page decision framework listing four criteria for plan and three for direct with a concrete example per criterion, explicitly noting ambiguity versus difficulty as the discriminator.
  6. Document the Explore isolation proof by showing that the main conversation after discovery contains only a summary while the subagent held the verbose graph.

Verify. Each task lands in its correct mode on arrival, the migration is consistent across files only under plan then execute, and the main window stays clean because discovery was isolated.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 3.514 min

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 time selects 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 transformation selects 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 considerations selects 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 transcription fork_session and 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 --fork flag that launches a separate top-level session. The version gate for the primitive is Claude Code v2.1.117 or later, with /fork on by default from v2.1.161 and controlled in any version by CLAUDE_CODE_FORK_SUBAGENT=1 to enable and 0 to force-disable, including any server-side staged rollout.
  • Session continuation flags. claude --continue is 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 cites claude --resume breach-timeline is 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 -p or --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 --pref style generic override and it should be treated as a distractor.
  • Memory layering. Project CLAUDE.md, walk-up directory CLAUDE.md files, and CLAUDE.local.md are 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 Read calls, 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_reason and 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-practices and claude-code-fork-session.
  • Self-improvement loop via CLAUDE.md. The best practices lesson defines the four-step loop of complete task, identify gaps, update CLAUDE.md with a concrete rule, and verify in the next session. This matters because corrections such as use async/await not then chains or run tests after every code change must compound across sessions rather than being re-explained in every refinement prompt. Slug best-practices.
  • Permission tiers as deterministic enforcement. The best practices lesson teaches allow, ask, and deny in .claude/settings.json as the layer that enforces policy, with CLAUDE.md prose treated as advisory. This matters because iterative work that includes destructive commands must not rely on prose discouragement but on deny rules. Slugs best-practices and configuration.
  • 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. Slug workflow-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.

example.ts
typescript
// 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);
}
example.ts
typescript
// 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);
}
result.json
json
// 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.

example.ts
typescript
// 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.

output.txt
text
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 total

Refinement 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.

instructions.md
markdown
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.

instructions.md
markdown
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.

output.txt
text
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.

terminal
bash
# 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 snapshot

Step 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.

terminal
bash
# 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 everywhere

Step 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.

terminal
bash
# 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.json

Observable. 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.

instructions.md
markdown
# Replace the prose prompt with two paired examples plus one naming sentence
# Save the new prompt to prompt-examples.md and run three times
output.txt
text
Transform 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" } }
terminal
bash
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.json

Observable. 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.

terminal
bash
# 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.txt

Observable. 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.

instructions.md
markdown
# 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.
terminal
bash
claude -p "$(cat interview-prompt.md)" > interview-questions.md
# Answer the questions, then proceed only after the answers are confirmed

Observable. 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.

terminal
bash
# 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.

terminal
bash
# 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 baseline

Observable. 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.

R1

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.

example.ts
typescript
// Input:
getUserData(userId: string): Promise<UserData>

// Expected output:
getUserData(userId: string): Promise<Result<UserData, ApiError>>
example.ts
typescript
// Input:
fetchOrders(customerId: string): Promise<Order[]>

// Expected output:
fetchOrders(customerId: string): Promise<Result<Order[], ApiError>>
result.json
json
// 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • - The base case uses two simple TypeScript function signatures and asks which technique to try first. A mutation swaps the language to Python logging rewrites or Go structured logging with minor-unit divisors, testing whether the solver transfers the rule across languages. - Another mutation adds that exhaustive CLAUDE.md prose improved simple one-to-one rewrites but left interpolated expressions and redaction wrappers inconsistent, isolating the idea that examples are specifically for the remaining edge-case variance. - A third mutation keeps prose plus a JSON schema in the prompt and shows that the schema alone did not stop format drift, reinforcing that concrete pairs dominate schema for mapping consistency.
R2

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation adds null handling with two sub-cases, field present but empty versus field missing entirely, and notes that each iteration fixes one while breaking the other. Another adds empty cells and quoted commas as the two edge rows that must appear as paired examples before the converter is written.
R3

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.ts
typescript
// 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.ts
typescript
// Example 2: locale display edge case
// Input
displayPrice = price.toFixed(2) + " " + currency;

// Expected output
displayPrice = Money.of(price).format(locale);
example.ts
typescript
// 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R4

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.

example.ts
typescript
// 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();
  });
});
output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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 the rate limiter with burst and reset edge failures automatically surfaced rather than discovered after manual testing, testing recognition of the workflow that surfaces failures without manual inspection.
R5

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.

output.txt
text
$ 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 total

Expected: 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation adds currency rounding and locale formatting as the two non-schema edge failures that must be shared together when they share the idempotency store path, while keeping malformed JSON separate. Another mutation replaces null in JSON with idempotency keys arriving within the same millisecond, testing whether the solver recognizes the same feedback discipline across domains.
R6

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.

instructions.md
markdown
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.
instructions.md
markdown
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation adds idempotency key collisions and currency rounding as the hidden edge cases the interview must surface before tests are written. Another swaps caching layer for rate limiting for the API gateway with per-user versus global scope, testing whether the solver maps the same pattern to a different infrastructure choice.
R7

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Mutations shuffle the domain while holding the symptom constant, for example moving the interview signal from caching to payment processing to notification channels, or moving the test-driven signal from rate limiter to CSV parser to currency converter, testing whether the mapping is symptom-driven rather than domain-driven.
R8

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation notes that a senior engineer suggests the problem is different mental models of comprehensive and still proposes prose refinement, forcing recognition that aligning mental models requires demonstration, not redefinition.
R9

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation keeps the checklist in the prompt and shows it improves coverage unevenly, then still requires examples to stabilize assertion style and edge case handling, reinforcing that the checklist is insufficient alone.
R10

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.

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation puts three entangled defects in one function to test batching. Another spreads them across two files plus a README typo.
R11

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.

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation flips the teamโ€™s current practice to force recognition of reverse guidance.
R12

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.

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation swaps date and currency for retry backoff plus job locking sharing a timer.
R13

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.

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation swaps React error handling for null preservation in JSON, testing whether the triad transfers across UI and data tasks. Another frames the stall as add more coverage producing regressions, where the triad becomes the concrete expected behaviors and edge cases as a targeted suite.
R14

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.

instructions.md
markdown
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation frames adding tests to 200 files, where mapping with Glob and Grep beats interruption.
R15

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation shows CLAUDE.md prose 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.
R16

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.

terminal
bash
claude --resume breach-timeline
# In the resumed session, explicitly refresh changed files
# Read the rebuilt files so the new contents replace the stale snapshot

Tool 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation makes the CI re-review re-flag already-fixed issues as unresolved when the only instruction is re-review the PR. Another replaces the investigation with a market analysis where web index refresh requires a fresh session before branching.
R17

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.

instructions.md
markdown
# 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
terminal
bash
# In the session, after context fills
/compact

The 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation isolates verbose discovery in a subagent per 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.
R18

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.

output.txt
text
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation replaces research narratives with containment narratives, another replaces API schemes with report structures, and a third adds the requirement that early strong designs keep diverging on shared pagination and error envelope contracts, testing whether the solver recognizes that fork alone does not enforce shared contract consistency.
R19

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.

terminal
bash
# 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.txt

A 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation adds Edit failures on files with near-identical response-construction lines and asks whether to anchor on unique signatures with Read then Write fallback, testing the deterministic editing discipline alongside the loop structure.
R20

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.

example.ts
typescript
// 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation lowers the ceiling and begins truncating legitimate long analyses.
R21

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.

output.txt
text
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 batch

Single-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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation replaces the iterative fix with /loop or CLAUDE.md, testing that the loop must be a script outside the single session.
R22

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.ts
typescript
// 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • A mutation adds three narrow clauses and shows conflict rate drops but persists, isolating reasoning-rich examples as the remaining cure.
R23

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.

terminal
bash
# 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation makes the symbol non-unique as timeout inside socket_timeout, testing Grep plus unique-anchor Edit with Read and Write fallback.
R24

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation keeps the clean suite and shows guidance to include messy variants.
R25

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.

output.txt
text
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 error

Related 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation swaps idempotency for retry backoff sharing a timer instance, another swaps malformed JSON for a log level one-word typo, testing that root-cause grouping is invariant.
R26

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • One mutation adds adaptive thinking and asks whether raising effort replaces the isolation, testing recognition that reasoning depth does not remove intent bias.
Type transform that diverged in prose, converged with pairs and tests
A production walkthrough with the reasoning chain made explicit.

A developer must transform every type signature returning Promise<T> into Promise<Result<T, ApiError>> as a discriminated union wrapper across a TypeScript codebase. The first attempt uses prose, convert every async function returning a plain value to a Result wrapper, and three runs produce three structures because plain value is interpreted differently each time, sometimes preserving existing error handling, sometimes replacing it, sometimes wrapping functions that already return rich objects.

The fix is two concrete pairs. Input getUserData(userId string) colon Promise<UserData> becomes Output getUserData plus Promise<Result<UserData, ApiError>>, and Input fetchOrders(customerId string) colon Promise<Order array> becomes Promise<Result<Order array, ApiError>>. With those pairs the model generalises the pattern that any Promise<T> becomes Promise<Result<T, ApiError>> and interpretation variance disappears on a new function not in the example set. The exam keys this as examples over better prose whenever runs diverge for the same description.

A more complex refactor still misses edge handling. The developer adds a test suite covering happy path, null inputs, empty arrays, and boundary conditions, runs it, and shares a failure such as FAIL testMigrationHandlesNullValues Expected null preserved in output JSON Actual null replaced with empty string. Claude sees the exact assertion mismatch and produces a targeted patch that preserves null, and each iteration reduces failing tests until the suite passes, which prose refinement could not do because the failure pinpoints the edge case identity.

A third shape is the unfamiliar domain. A caching layer for a backend API receives the prompt I need a caching layer for the API, before implementing ask me questions about requirements, edge cases, and constraints I should consider, and Claude asks about invalidation strategies such as TTL, event, and write through, TTL policies such as fixed, sliding, and adaptive, consistency as strong versus eventual, and failure modes such as miss, stampede, and stale data. The developer learns what questions matter rather than prescribing a solution that omits them. Batched versus sequential delivery appears in the review pass, three interacting fixes for error shape, logging, and SDK type are batched together while naming and indentation are sequenced separately, producing coherent alignment only when the interacting set is seen together.

Distinctions that decide answers

ThisNot thisHow to tell them apart
Concrete examplesTest driven iterationExamples 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 examplesInterview patternExamples 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 feedbackSequential feedbackBatched 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 refinementExamplesRefined 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 judgementConfidence threshold filteringFew 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

Refining prose when interpretation diverges

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.

Confusing interview with examples

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.

Confidence thresholds to fix judgement inconsistency

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.

Batching independent issues

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.

Too many examples beyond the cost benefit sweet spot

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.

Skipping test driven iteration when tests exist

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.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Few shot structure with reasoning for judgement

Each example should be a complete input to output demonstration and for judgement tasks should include reasoning not just the pair, so the model learns the decision principle rather than surface pattern alone.

Claude Code Slash Commands
In context learning tradeoffs for example count

Zero shot cheapest but least reliable for format sensitive work, two to five best ratio, many shot five plus helps nuanced tasks at higher token cost, with placement at system prompt end near the task for maximal influence.

Configuration
Evaluator optimizer as architecture level framing

Generator produces output, evaluator scores it via tests or rules, optimizer refines until criteria pass, which formalises test driven iteration as the in session evaluator optimizer with failures as the evaluator signal.

Workflow Patterns
Build it
Prove examples beat prose, tests beat prose for edge cases, and batching matters
  1. Describe a code transform in prose, run it three times with the same prose, and record how interpretation varies in naming, edge handling, or structure.
  2. Replace prose with two or three input output pairs for the same transform, run three times, and compare consistency to the prose only trials, noting guidance about generalisation to a new case not in the pair set.
  3. Write a test suite with happy path, edge cases such as null or empty, and a performance budget, run it, share the failure lines Expected X got Y with Claude Code, and iterate until the suite passes, recording iteration count reduction versus prose only steering.
  4. Use the interview pattern for a task outside your expertise such as a caching layer, prompting Claude to ask about requirements before implementing, and log which considerations surfaced that you had not anticipated such as invalidation, TTL, consistency, and stampede handling.
  5. Batch three interacting fixes such as error shape, logging format, and SDK type in one message versus sequencing them, observing coherence of the single patch versus per step contradictions.
  6. Produce a one page technique chooser mapping inconsistent interpretation to examples first, complex transformations to tests first, and unfamiliar domains to interview first, with batch versus sequential delivery as a second decision.

Verify. The transform converges with pairs where prose diverged, edge cases converge only when driven by test failures, unfamiliar domains produce a more complete design under the interview, and interacting fixes align only when batched.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 3.618 min

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.

terminal
bash
# 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.txt

What 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:

  • text is the default. The final response is plain text. Human-readable and suitable when a person will read the log. No envelope, no cost metadata.
  • json emits a single JSON envelope after the run finishes. The envelope carries the answer in result plus machine fields such as session_id and total_cost_usd. This is the correct choice when an automated consumer must parse the result, log spend, or post findings programmatically.
  • stream-json emits 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 with type equal to result. A consumer that needs live progress, a tailing log, or partial rendering should use this mode; a consumer that only acts after completion should use json.

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:

  • text is the default. The prompt is a single string argument, optionally augmented by piped stdin.
  • stream-json lets 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 additionalProperties where strictness is wanted. The format keyword is accepted as an annotation but is not enforced.
  • On success, the envelope contains both result and structured_output; the latter is the extracted typed object. On schema-retry exhaustion the envelope may lack structured_output or carry a subtype indicating error_max_structured_output_retries. A gate that only checks the exit code can let a contract violation pass; it must also assert the presence of structured_output.
  • Extraction is jq '.structured_output' or a stricter assertion jq -e '.structured_output'. Parsing result is the wrong field for the typed contract.
  • Streaming and schema interact independently. Schema validation governs the final shape; stream-json governs delivery. Neither removes the need for -p.
terminal
bash
# 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 1

What 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.

terminal
bash
#!/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.json

What 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.

result.json
json
// 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
}
terminal
bash
#!/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.

workflow.yml
yaml
# 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.json

What 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:

  • default runs standard permission checking. In -p mode any unapproved action aborts the run because no one can answer a prompt. Rarely useful alone in CI.
  • acceptEdits auto-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.
  • dontAsk auto-denies anything not explicitly allowed via permissions.allow or the built-in read-only command set. This is the locked-down baseline for read-only CI that should only report, never write.
  • bypassPermissions skips permission prompts entirely. Functionally equivalent to --dangerously-skip-permissions as a standalone flag. Reserved for trusted, sandboxed environments and can be disabled org-wide via a managed setting.
  • plan is 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's CLAUDE.md; loading extra-directory memory is gated by the environment variable CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1.
  • --model <alias|name> selects sonnet, opus, or a full model id for the headless session.
  • --bare skips discovery of hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.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.

settings.json
json
// .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" }
        ]
      }
    ]
  }
}
terminal
bash
#!/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 prompting

What 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.

terminal
bash
# 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:

  • -c and --continue resume the most recent conversation in the current directory.
  • -r and --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.

terminal
bash
#!/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.json

What 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:

terminal
bash
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.

terminal
bash
# 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 1

Mechanism 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.

GuaranteeOwnerHow it is observedFailure signal
Terminating headless executionCLI layer (-p and --print)Process writes to stdout and exits; runner sees a finite jobHang until runner timeout, no stdout
Machine-parseable shapeCLI layer (--output-format json and stream-json)Single JSON envelope or line-delimited JSON streamProse scraping breaks on wording drift
Typed payload with exact fieldsApplication contract via CLI (--json-schema plus structured_output)structured_output present with required fields and valid typesMissing structured_output or subtype error variant
Bounded cost and latencyApplication layer (--max-turns) plus runner timeoutTurn count capped; job finishes within budgetOpen-ended loop, cost spike, wall time overrun
Permission policy per runCLI plus configuration layer (--permission-mode, --allowedTools, --disallowedTools, --tools, settings.json permissions)Tool calls succeed or are denied without prompting; deny wins over modePrompted or hung on approval, or disallowed tool executed
Session correlator and resumptionCLI layer (session_id plus -c and --continue, -r and --resume)Second step resumes prior session or starts isolated one as intendedLost context or unintended bias from shared transcript
Project context for CIConfiguration layer (CLAUDE.md hierarchy, CLAUDE.local.md excluded)CI run follows team testing and review standardsGeneric boilerplate, duplicated tests, wrong severity
Deterministic versus probabilistic controlArchitecture split: enforced controls live in CLI and config, advisory controls live in model promptEnforced deny or tool removal blocks regardless of wording; prompt-only instruction may driftModel acts despite instruction, especially under injected tool output
Secret presence for API credentialInfrastructure layer (ANTHROPIC_API_KEY in protected store) plus CLI error pathRun authenticates immediately; missing key fails fast with non-zero exitHang or opaque auth error mid-run
Streaming delivery without lossCLI 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 -p and --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 -p and --print.
  • Structured output is --output-format json with an optional --json-schema for a typed contract. Older material that says --json or --output-file is using a non-existent spelling. The schema requires JSON Schema draft-07 and requires --output-format json in the same command.
  • Read-only control is --permission-mode plan. References to a standalone --read-only flag should be read as the plan mode unless the current CLI reference is re-checked and shows otherwise. Treat --read-only as unverified against the present docs.
  • Autonomous permission bypass is --dangerously-skip-permissions as a flag and --permission-mode bypassPermissions as the mode value. Older material may call this --yes or --no-confirm. Those are distractors, not synonyms.
  • CLAUDE.md loading in headless runs applies by default and is skipped only with --bare. The additional-directories gate CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 for --add-dir memory 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, and results_url contract. 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 -p and --print as 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 --json as a short flag, or placing the schema in the prompt. Documentation defines --output-format json plus --json-schema with a draft-07 schema and a dedicated structured_output envelope 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.md instructions such as never run a command or on post-action audit hooks to prevent edits. Documentation treats settings.json deny rules, --disallowedTools, and --tools restriction as the hard boundaries, with PreToolUse hooks 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.md hierarchy and concatenation. CI runs load global, project, local, and lazily directory-level files as concatenated layers, not overrides. Understanding this explains why a shared project CLAUDE.md already shapes CI output without extra workflow configuration.
  • @path import 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.json precedence from user through project and local to --settings to managed, with managed always winning, scalars overriding and arrays concatenating. Pipeline isolation depends on this order.
  • $schema hint https://json.schemastore.org/claude-code-settings.json for editor validation of settings files. A misspelled deny rule is otherwise silent.
  • Enterprise keys companyAnnouncements, disableBundledSkills, skillOverrides, and availableModels. 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, and agent, and a structured hookSpecificOutput decision that merges by deny greater than defer greater than ask greater than allow.
  • claudeMdExcludes for tidy discovery and --add-dir gate CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD. Extra directories are reachable to tools but do not load extra CLAUDE.md without the opt-in variable.
  • SDK selectors settingSources and systemPrompt.preset. An SDK-hosted pipeline that ignores CLAUDE.md is usually missing a setting_sources entry, not a broken file path.
  • Agent SDK programmatic controls canUseTool, permissionDecision values including ask for human-in-the-loop, permission_mode baselines, max_budget_usd and the error_max_budget_usd subtype, and the CLAUDE_CODE_USE_BEDROCK and CLAUDE_CODE_USE_VERTEX routing 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.
  • /hooks inspection plus PreToolUse, PostToolUse, Notification, and Stop event 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:

terminal
bash
#!/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:

terminal
bash
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.json

Push 1, independent review:

terminal
bash
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.json

Push 2, incremental review:

terminal
bash
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"
done

Reasoning 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.md that declares testing standards and review criteria.
  • Step 2: write a shell script that runs claude "Review this diff" without -p behind 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 -p to the same command and run again. Observable: the process writes to stdout and exits with status zero within a few seconds, producing a result field when --output-format json is also added.
  • Step 4: add --output-format json and parse with jq -r '.result'. Observable: jq extracts the result without error and jq -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 -p variant. 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.json matching the draft-07 schema used earlier, requiring verdict, reasons, and findings with file, line, severity, and message.
  • Step 2: run claude --bare -p --output-format json --json-schema "$(cat verdict.schema.json)" "Return a pass fail verdict" and write to gate.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.json gates the pipeline correctly and that total_cost_usd and session_id are 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 plan or 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-only flag is not listed in claude --help or the CLI reference. Observable: help output does not show it as a current flag, confirming plan is 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.md listing 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=1 with claude --add-dir ../shared-lib and 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 acceptEdits and store gen-result.json plus session_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 --continue or --resume to the generation session. Observable: two distinct session_id values in gen-result.json and review-result.json.
  • Step 3: store review-result.json's structured_output as findings-last-run.json and simulate a second push that injects it with the suppression instruction. Observable: the second review's structured_output.findings contains only new or still-present items.
  • Step 4: confirm the posting loop reads from structured_output with jq -c '.structured_output.findings[]' rather than from result or 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 pipefail and three gates: exit status via the shell, jq -e '.structured_output', and jq -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 matching permissions.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 subtype signals the error variant with a missing structured_output, caught by the second gate.
  • Step 4: enable --output-format stream-json --verbose --include-partial-messages on a long run, consume as line-delimited JSON, and gate only on the final type: "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.

R1

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.

terminal
bash
# 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Some items add --output-format json to 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.
R2

`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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Sometimes the variable is presented as CLAUDE_MODE=CI or CLAUDE_OUTPUT=json. Both are equally fabricated and serve the same distractor purpose: a config-looking knob that does not exist.
R3

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • In some items --batch is combined with --format=json to look like a complete automation invocation. The combination is doubly wrong: neither flag is real.
R4

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R5

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R6

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Some items ask whether --output-format json alone 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.
R7

`--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.

terminal
bash
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Some items ask whether --json-schema alone 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 is structured_output. Both are tested as traps.
R8

`--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.

terminal
bash
# 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-json

A 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items sometimes conflate streaming with interactivity: streaming still needs -p. Others ask whether streaming removes the need for a schema. It does not; --json-schema applies to print mode regardless of streaming.
R9

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items may ask whether --input-format fixes a hang. It does not; interactivity is still governed by -p.
R10

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.

terminal
bash
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
fi

A 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).

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R11

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.

terminal
bash
claude -p "Emit the verdict" --output-format json --json-schema verdict.schema.json \
  | jq -e '.structured_output' > /dev/null || exit 1

The 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.)

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R12

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.

terminal
bash
# 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R13

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.

terminal
bash
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R14

`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.

instructions.md
markdown
# .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.json

A 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).

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R15

`--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).

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items rotate the distractor flags (--yes, --no-confirm, --headless); the correct answer is invariant. Some pair it with -p to emphasize both interactivity and permissions must be solved.
R16

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R17

`--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.

settings.json
json
{
  "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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items ask whether dontAsk overrides a deny rule. It does not. They ask whether bypassPermissions overrides a deny rule. It does not. The invariant: deny wins over mode.
R18

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items ask whether --allowedTools restricted to [Read, Grep, Glob] lets the agent fix a found vulnerability. It does not: without Write/Edit it can only report. That is the hard-boundary rule (19), tested through the grant flag.
R19

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R20

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items rotate advisory vs enforced across many surfaces; the invariant is "enforced wins when unattended."
R21

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.

terminal
bash
#!/usr/bin/env bash
# PreToolUse hook: block forced pushes
read -r input
echo "$input" | grep -q "git push --force" && exit 2
exit 0

Prevention 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items ask whether a PostToolUse hook prevents the violation. It does not. They ask whether exit 1 blocks. It does not. Both are traps around the same rule.
R22

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items test whether dontAsk overrides an ask rule (no) or whether bypassPermissions overrides deny (no). The invariant is the order.
R23

`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.

config.yaml
yaml
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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items pair the key question with the -p question to separate "fails fast" from "hangs." The distinction is the teaching point.
R24

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items rotate which credential is proposed to override the key; none below it can. The invariant is the chain order.
R25

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.

settings.json
json
{
  "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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R26

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R27

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.

terminal
bash
# 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 endpoint

A 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items rotate the workflow type; the invariant is blocking needs real-time, tolerant needs batch.
R28

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items test append-versus-replace directly; the invariant is append keeps defaults, replace drops them.
R29

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items ask whether --bare loads CLAUDE.md; it does not. The invariant is skip-discovery.
R30

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • 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.
R31

`--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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items ask whether verbose alters execution; it does not. The invariant: visibility, not behavior.
R32

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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items test whether subagent autonomy can be capped per-child; it cannot under bypassPermissions. The invariant: deny rules are the cross-mode control.
R33

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.

terminal
bash
claude -p "Stream the review" --output-format stream-json | while IFS= read -r line; do
  echo "$line" | jq -e '.type' > /dev/null || true
done

Streaming 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.

Wrong answers written against this rule

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.

How the same rule gets re-asked
  • Items ask whether streaming changes the schema requirement; it does not. The invariant: stream is many objects, single payload is one.
Pre merge PR check that stops hanging and starts posting inline findings
A production walkthrough with the reasoning chain made explicit.

A team runs Claude Code as a pre merge check in GitHub Actions with the job claude Review this PR for security issues. The job hangs in the log with Claude waiting for interactive input, and the fix is -p, claude -p Review this PR for security issues, after which the run completes and stdout is captured by the runner. The exam presents this as Question 10 in the sample set and the distractors that do not exist remain wrong. Variants such as CLAUDE_HEADLESS, --batch, or stdin redirection are not the documented mechanism.

The next improvement makes the findings usable. claude -p --output-format json --json-schema object with findings array items containing file string, line integer, severity string, and message string produces a JSON envelope where jq dot structured_output extracts the validated array. Each finding has file, line, severity, and message, so the runner posts an inline PR comment at the exact location with severity visible in context rather than dumping a blob of text at the end of the log.

Context isolation is added for a workflow that both generates and reviews. The runner runs claude -p Implement the authentication middleware as session A then claude -p Review the authentication middleware for security, error handling, and edge cases as session B with no shared history. Session B evaluates the code on its merits because it does not carry session A reasoning about why the middleware was designed that way, which is the correct fix for the same session self review that is cheaper but less thorough.

Incremental hygiene and project context close the loop. The runner stores first findings as a JSON artifact and on subsequent pushes runs claude -p --output-format json Review this PR given prior findings from PREVIOUS_FINDINGS, report only new or still unaddressed issues, do not re report deferred ones, so fixed items drop out and only fresh or persistent problems reappear. A CLAUDE.md section for CI documents testing standards such as factory pattern from test slash factories, integration setup via test slash setup slash db dot ts, no private implementation detail testing, available fixtures under test slash fixtures, and severity criteria, so headless generated tests follow team patterns rather than generic boilerplate.

Distinctions that decide answers

ThisNot thisHow to tell them apart
-p flagCLAUDE_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 textjson 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 reviewIndependent review instanceSame 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 APIMessage Batches APISynchronous 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 replaceAppend 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 toolsdontAsk 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

Fixing a hanging CI job with a nonexistent flag

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.

Same session self review is as good as independent review

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.

Batch API for blocking pre merge checks

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.

No incremental review context

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.

Prompt only ban on destructive commands

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.

No CLAUDE.md for CI

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.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
--bare for deterministic scripted runs

Skips hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.md leaving only Bash and file read edit tools, so the same scripted command produces the same result on every runner where project configuration must not affect the outcome.

CI/CD Integration: --print and Non-Interactive Flags
Stream json for live progress in CI logs

--output-format stream-json with --verbose and --include-partial-messages streams NDJSON where the last line has type result, filterable with jq over stream_event text_delta for log forwarding without blocking silently.

Workflow Patterns
Permission least privilege as headless complement to interactive permissions

Interactive .claude/settings.json allow ask deny and headless --permission-mode plus --allowedTools share the same least privilege principle, with over permission causing accidents and under permission causing friction in both shells.

Best Practices
Build it
Wire a pre merge check that blocks plus posts plus preserves context
  1. Write a CI script that runs claude -p with a review prompt for the PR diff, capturing stdout as JSON envelope with --output-format json and verifying the job no longer hangs waiting for input.
  2. Add --json-schema with a schema requiring findings array items with file string, line integer, severity string, and message string, then pipe output to jq dot structured_output and assert schema conformance before any inline posting.
  3. Extend the script to parse the validated findings and post each as an inline PR comment at the exact file and line with severity visible, plus a summary dashboard artifact with session_id and total_cost_usd.
  4. Add a CLAUDE.md CI section listing testing standards such as factory from test slash factories, setup via test slash setup slash db dot ts, 80 percent branch target, and fixtures under test slash fixtures, then verify headless generated tests use the project's factories rather than generic boilerplate.
  5. Split generation and review into two independent claude -p invocations sharing no session context and compare findings versus a same session self review to confirm more thorough review under isolation.
  6. Implement incremental review by storing prior findings as a JSON artifact, including them in the next run's prompt, and asserting that fixed issues drop out while new or still open issues remain, with batch API only for the overnight variant and synchronous for the blocking gate.

Verify. The CI gate runs headlessly without hanging, findings are deterministically validated and appear inline, isolated review is more thorough than self review, and repeated pushes report only new or still open issues.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Claude Code Configuration and Workflows exam
12 questions, the same number this domain contributes to the real 60-question paper. Drawn fresh from a larger pool each attempt, so a retake is a different paper. You can reveal the answer to any question while taking the exam, which locks that question. Otherwise answers stay hidden until you submit.
Cross-cutting decision table
DecisionChoose thisOver thisWhy
Where team conventions should liveProject CLAUDE.md or .claude/rules with paths shared via gitUser ~/.claude/CLAUDE.md on one machineProject scope reaches every clone, user scope never reaches a new teammate and causes drift that appears only for the newcomer.
Flat versus skill slash command for a team workflow.claude/skills/name/SKILL.md with supporting files and description.claude/commands/name.md only where skill features are neededSkill path adds directory support and auto discovery and wins on name collision, flat path is alias only.
Verbose workflow isolationSKILL.md with context fork so the fork holds noise and the main session receives a summaryInline skill that streams exploration into the main windowMain window isolation preserves context budget and quality for implementation, inline noise degrades subsequent turns.
Sensitive workflow that should not auto firedisable-model-invocation true plus Skill(name) permission ruleDescription alone or allowed-tools as the controlExplicit only requires the model to be removed from automatic invocation, tool allow listing governs a different axis.
Cross directory test conventions.claude/rules/testing.md with paths star star slash star dot test dot tsxCLAUDE.md per test directoryOne rule with one glob covers every test file even in new directories, directory per test directory drifts and needs manual addition.
Always on versus on demand checkRules in .claude/rules for always on background guidanceSkills for always on guidanceRules stay in context as guidance, skills load as task workflows, always on belongs in the always in layer.
Ambiguous multi file change versus clear single file fixPlan mode or plan then execute when multiple approaches or architectural choice existsDirect execution when scope is clear and approach is knownAmbiguity at arrival is the discriminator, not difficulty, plan avoids rework on the former, direct avoids overhead on the latter.
Discovery output handlingExplore subagent whose result returns as a tool_result summaryMain agent listing and graphing directlySubagent isolation keeps the main window clean, main agent exploration clutters it with listings that persist for every later turn.
Noisy interpretation versus complex edge casesExamples for inconsistent interpretation of a known transform, tests for complex edge heavy transformsMore prose for eitherExamples close interpretation room, tests provide unambiguous Expected X got Y feedback, prose leaves room in both cases.
Hanging CI jobclaude -p print mode with headless flagsCLAUDE_HEADLESS or --batch or stdin redirectionOnly -p is the documented headless gate that processes, emits to stdout, and exits without keyboard.
CI findings posting--output-format json plus --json-schema draft-07 into structured_output then jqPrompt only shape instructionsDeterministic validation with internal re prompt versus probabilistic drift under tool pressure.
Review quality under pipeline budgetIndependent claude -p invocations for generate versus review plus incremental prior findings carry forwardSame session self review without prior artifactIndependent removes generation bias and incremental avoids duplicate comments that erode trust across pushes.
Why wrong answers keep looking correct
Deeper CLAUDE.md silently wins and instructions are overridden like settings. CLAUDE.md concatenates, both instructions sit in context and the model may pick either, while settings.json scalars replace layer by layer. The override intuition applies only to settings, not to memory as guidance.
Flat review.md inside .claude/skills should create slash command /review because the path looks like a skills command. Skills require a directory containing SKILL.md, loose files there create no command, while the flat command path is .claude/commands/name.md. The directory shape is the discriminator.
Allowed-tools strictly removes tools while active, so it is the security boundary. Allowed-tools pre approves listed tools for promptless use while other tools remain callable, the true removal boundary is disallowed-tools or settings.json deny rules. The name suggests restriction but the current runtime implements grant not removal.
More precise prose will fix runs that diverge for the same description. Prose refinement still relies on interpretation, so variance persists, while concrete input output pairs eliminate interpretation room by giving the model the pattern to generalise directly.
Same session review is cheaper and just as thorough as independent review. The generation session carries its own design rationale and is less likely to question it, so findings are measurably less thorough for the same code. Cost savings trade directly against objectivity.
Batch API cost savings apply equally to blocking pre merge checks. Batches run with up to 24 hour window and no SLA, most finish within an hour but none is guaranteed for a blocking gate, so only overnight latency tolerant work is safe for the 50 percent savings, not a merge gate that must answer in minutes.
Last five minutes
Rules
  • If two memory locations conflict, choose concatenate not override, and move the must hold rule to settings.json or a hook rather than trusting CLAUDE.md ordering.
  • If a new teammate drifts from team conventions, the fault is user versus project CLAUDE.md scope, fix by moving the rule from ~/.claude/CLAUDE.md to project CLAUDE.md.
  • If a command should be on demand, put it in .claude/skills/name/SKILL.md with description driven auto invoke, if always on, put it in CLAUDE.md or .claude/rules with paths.
  • If verbose output clutters the main conversation, add context fork to the skill so the fork holds the noise and the main session receives only the summary.
  • If the skill should be explicit only, set disable-model-invocation true and gate with Skill(name) permission, not description alone.
  • If the convention follows file type across many directories, choose path scoped rules with star star slash star dot pattern, not CLAUDE.md per directory, and verify with /context while editing a matching file.
  • If the stem states multiple valid approaches or architectural choice, choose plan or plan then execute, if the stem gives a clear stack trace and single function location, choose direct execution.
  • If inconsistent interpretation persists across prose attempts, provide two or three input output pairs, if complex edge cases persist, share test failures as Expected X got Y.
  • If the CI job hangs waiting for input, the missing flag is -p, if the output must be machine parsed, add --output-format json plus --json-schema for deterministic validation into structured_output.
  • If the pipeline both generates and reviews, use independent claude -p invocations and carry prior findings forward to avoid duplicate comments.
Trigger phrases

Look for new team member versus same branch, flat file inside .claude/skills versus SKILL.md directory, context fork versus inline, disable-model-invocation versus allowed-tools, claudeMdExcludes versus permissions, star star slash star dot test dot tsx versus single directory CLAUDE.md, plan versus direct via stated ambiguity, examples versus interview versus test failures, -p versus CLAUDE_HEADLESS, json plus schema versus prompt only shape, same session versus independent review, batch 50 percent versus real time seconds to minutes.

If you see X, think Y

If you see @path written without an @import keyword but with code span skipping and four hop limit think CLAUDE concatenation and lazy rules. If you see /memory think known locations keyed answer, if you see /context think actual memory files in the running session. If you see --add-dir reachable but no CLAUDE.md loaded think the gate env var not set. If you see skill.json or --install-skill think distractor. If you see prompt only ban on destructive commands think probabilistic not enforceable. If you see test dot ts co located widely think path scoped rules not directory memory.