๐ŸŽ“ Claude Architect KB CCAR-F ยท CCAR-P study guide

Domain 2 โ€“ Facts

Claude Code ยท the things to remember, in plain words

Each item is one rule that decides exam questions. Read the short headline, say the rule to yourself, then read the line to check. Open why if the reason is not clear, and follow the question link to see the rule in action. Collected from the lesson, the practice questions and two full third-party mock exams. They help you remember; they do not guarantee a score.

Back to Domain 2 lesson ยท Domain 2 โ€“ Questions ยท Domain Q&A

Memory files & rules

  1. Which CLAUDE.md wins? None of them wins. Claude Code adds them all together into context: ~/.claude/CLAUDE.md, the project CLAUDE.md, and CLAUDE.local.md. If two rules disagree, the model may follow either one. "The more specific file wins" is the trap. โŒ wrong choice to avoid: keeping the rule in the project CLAUDE.md because "the more specific file wins"
    why

    Claude Code reads every memory file that applies to your folder and appends them, broadest first. The docs call this a load order, not a priority order: nothing deletes the earlier text, so both rules stay in context. That's why a project rule can't reliably beat a developer's personal ~/.claude/CLAUDE.md. If a rule must hold every time, put it in settings permissions or a hook โ€” the client enforces those, whatever the model decides.

  2. Rule must always hold? If a rule must hold on every run, don't leave it as text โ€” move it to settings.json permissions or a hook. Moving the text to CLAUDE.local.md only changes the load order, and that file is gitignored, so teammates never get it. โŒ wrong choice to avoid: moving the rule to CLAUDE.local.md so it loads last and "wins"
    why

    CLAUDE.md text is guidance for the model, with no guarantee it is followed. Permissions and hooks are run by the Claude Code client itself: a deny rule stops the call, and a PreToolUse or PostToolUse hook runs as a shell command no matter what the model wrote. Putting the sentence in CLAUDE.local.md just makes it the last file added; the conflict is still in context. And because that file is gitignored, a team rule there never reaches teammates.

  3. When does a nested file load? Files above your folder load when the session starts. A nested services/billing/CLAUDE.md loads only when Claude starts working in that folder. So if the rules belong to one folder tree, put them in a nested file there.
    why

    At session start, Claude Code reads the memory files above the working directory. A CLAUDE.md inside services/billing/ stays unread until Claude touches files under that path; then it is pulled in and adds to the parent files โ€” it does not replace them. That keeps billing rules out of unrelated sessions with no extra setup. If the rules belong to a file type spread across many folders, use a paths: rule instead.

  4. Rules for one file type? Put them in .claude/rules/*.md with paths: ["**/*.test.ts"] frontmatter. Then the rule loads only when Claude edits a matching file, anywhere in the repo. A rules file without paths: just loads at session start. โŒ wrong choice to avoid: a section heading in the root CLAUDE.md, or copying CLAUDE.md into every folder
    why

    Rules files let you split one big CLAUDE.md into topic files. Add a paths: list of glob patterns (file-name patterns) and the file becomes conditional: it enters context only when Claude edits a matching file, wherever it lives. That saves tokens and stops, say, Terraform rules showing up in React work. A section heading in the root file, a copy of the file in every folder, or a slash command you must type yourself โ€” none of these switch on automatically by file pattern.

  5. File type or folder? Ask: is the rule about a file type or a folder? A file type spread across folders โ†’ a paths: rule. One folder tree โ†’ a nested CLAUDE.md. A heading in the root file always loads, and @import loads everything too.
    why

    Pick the tool by the shape of the rule. Rules for a file type that shows up in many folders (test files, .tf files) need a .claude/rules/ file with paths: globs, because it matches by pattern. Rules for one folder tree need a nested CLAUDE.md, because it loads by location. A heading in the root file loads with everything else, and an @import line pulls its file in at session start โ€” neither one is conditional.

  6. Team rule or personal? Team rules go in the committed project CLAUDE.md. Your ~/.claude/CLAUDE.md is not in git, so teammates and CI never see it. If the team needs a rule, it can't live only in your home folder. Practice Q&A ยท Q10 ยท โŒ wrong choice to avoid: each engineer's settings.local.json, or a missing "activation header" on the user-level file
    why

    The user-level file sits in your home directory and applies to every project you open, but it is never part of the repo. When a teammate pulls or CI checks out the code, only committed files travel: the project CLAUDE.md, .claude/rules/, settings and skills. A standard written only in ~/.claude/CLAUDE.md looks like "no standard" to everyone else. Keep personal preferences in the user file; put team standards in the project file and review them like code.

  7. What did this session load? Run /memory. It lists the CLAUDE.md and rules files this session actually loaded. Asking Claude to repeat the rule, deleting files, or running /compact proves nothing. โŒ wrong choice to avoid: asking Claude to repeat the rules back, or running /compact to "reload" config
    why

    /memory gives a sure answer: it prints the user, project and folder-level memory files in this session, so a stray user file or a missing project file shows up right away. Asking the model to repeat the rule proves nothing โ€” it can answer from training data or partial context. Deleting ~/.claude/CLAUDE.md is a destructive guess. /compact only summarizes the conversation; it does not reload or report config.

  8. Quick memory shortcuts Four shortcuts: start a message with # to add a memory line; /memory to edit the files; /init to create a starter CLAUDE.md for a repo; @docs/architecture.md to import a file. Imports still cost context.
    why

    A message that starts with # adds that line to a memory file without opening an editor. /memory opens the loaded files for editing. /init scans the repo and writes a first CLAUDE.md with its build and test commands. An @path line inside a memory file pulls in another file when it loads. It keeps the root file short, but the imported text still uses context every session โ€” tidier, not cheaper.

  9. Rules for one client only? Put them in a subdirectory CLAUDE.md (for example in clients/acme/) and run that job from there. It adds to the parent file without changing it. --append-config is not a real flag. โŒ wrong choice to avoid: passing the client rules in the user message at the start of each run
    why

    Memory files stack by folder: a CLAUDE.md in clients/acme/ is added on top of the root file, so the stricter rules apply only when the job runs from there, and every other run keeps the defaults. CLAUDE.md has no if-logic, so an "if the brief contains CLIENT_ID" block is just text. --append-config doesn't exist; the real --append-system-prompt-file flag adds to the system prompt, which is a different mechanism. Putting the rules in the user message works once, but it is not config.

  10. Wrong kind of tests? If Claude writes mocked unit tests when you wanted integration tests, your rule was too vague. Spell out the criteria in CLAUDE.md: real database, contract assertions. A rule or hook that bans all mocks goes too far. โŒ wrong choice to avoid: a .claude/rules/ file on test files that tells Claude never to use mocks
    why

    The usual cause is that the instruction never said what an integration test must include. Concrete criteria โ€” a real database connection, API contract checks, migration rollback, an example fixture โ€” give the model a clear target and fix it at the source. A paths: rule or PostToolUse hook that rejects every mock also kills good unit tests. A review subagent that rewrites tests afterwards is less reliable and more complex than just saying what you need.

Settings, permissions & precedence

  1. Which setting wins? Strongest first: managed policy โ†’ CLI flags โ†’ .claude/settings.local.json โ†’ .claude/settings.json โ†’ ~/.claude/settings.json. Remember: a CLI flag never beats managed policy. Practice Q&A ยท Q45 ยท โŒ wrong choice to avoid: CLI flags above managed policy, or settings.local.json above CLI flags
    why

    For single-value settings, these layers have a strict order, and the exam tests the whole chain. Managed policy comes from your organization, and nothing below can override it. A command-line value beats every file. Your personal, gitignored local project file beats the shared project file. Your home settings are weakest. This order is for settings only โ€” CLAUDE.md text is added together, not ranked.

  2. Three ways config merges Config merges three different ways. Single values like model override (the strongest layer wins). permissions.allow/deny lists add up across all layers. Memory files are just joined together. An answer that calls all three "the hierarchy" is wrong.
    why

    So it depends on the key. A single value such as model follows the precedence chain. Permission lists (allow, ask, deny) are combined from every layer, which is why a deny anywhere still applies. Memory files are pasted into context with no ranking at all. An answer that describes one hierarchy for everything is wrong about at least one of the three.

  3. Does deny beat allow? Yes, always. A matching deny beats any allow from any layer, even --allowedTools. And removing an allow entry is not a deny โ€” it only means Claude Code asks you instead of running the call automatically. โŒ wrong choice to avoid: a PreToolUse hook that waits for human approval, or a new request tool that needs server changes
    why

    Claude Code checks deny rules first: if any layer denies a tool pattern, the call is blocked, however many allows match. It is not symmetric โ€” dropping a tool from the allow list only removes pre-approval, so you get a prompt, not a block. To really take a tool away, use deny, disallowedTools or --tools. For a pipeline that needs human approval, hard-block the tool and tell the model to write a request for the human queue: a PostToolUse hook fires after the damage, and a PreToolUse hook can't pause and wait for a person.

  4. How to read a rule A rule is Tool(pattern). Bash(npm run test:*) means the Bash tool, only for commands starting npm run test. deny: ["Read(.env*)"] makes secret files unreadable. mcp__docs__load_document names one MCP tool.
    why

    Bash(npm run test:*) pre-approves only shell commands that start with npm run test; * is the wildcard, and other Bash commands are not covered. Read(.env*) in the deny list stops the model from reading any file whose name starts with .env, so secrets never enter context. MCP tools use the same system with the name mcp__<server>__<tool>, so you can allow one tool on the docs server and deny another one from the same server.

  5. Just my own setting? If a setting is only for you in this project, like "model": "claude-haiku-4-5", put it in .claude/settings.local.json โ€” it's gitignored. .claude/settings.json is committed, so every teammate gets it. Practice Q&A ยท Q41 ยท โŒ wrong choice to avoid: putting the model key in the committed .claude/settings.json, or as text in CLAUDE.md
    why

    The two project files differ only in who sees them. .claude/settings.json is committed, so anything there is a team decision that every clone inherits. .claude/settings.local.json is your personal override for this checkout; it beats the shared file and must stay out of git. Writing "use Haiku" as text in CLAUDE.md does not set the model. Managed policy is for company-wide controls, not one person's preference.

  6. What managed settings do Think of managed-settings.json (in /Library/Application Support/ClaudeCode/ or /etc/claude-code/) as a clamp: nothing below can loosen what it sets. But it only controls the keys it actually sets; lower layers decide the rest.
    why

    IT puts managed settings in a system path normal users can't edit โ€” /Library/Application Support/ClaudeCode/managed-settings.json on macOS, /etc/claude-code/ on Linux. Any key it sets โ€” a deny on reading .env files, disableBypassPermissionsMode, a forced login method โ€” sits at the top, and because permission lists add up, no lower file can cancel them. It is a clamp, not a full config: keys it doesn't mention are left to the layers below.

  7. Skip all permissions? --dangerously-skip-permissions is only for isolated containers or CI. On the exam, the right answer is scoped allowlists. A company can switch the flag off everywhere with managed disableBypassPermissionsMode: "disable". Practice Q&A ยท Q12 ยท โŒ wrong choice to avoid: --dangerously-skip-permissions, or just trusting the model to follow policy
    why

    The flag removes the permission prompt completely. That's handy in a throwaway container, but it deletes the exact check a security question wants you to keep. The expected design is narrow allow rules like Bash(npm run test:*), credentials that can't publish, and a real network sandbox โ€” controls that still work when the model makes a mistake or reads malicious input. To ban the flag org-wide, set disableBypassPermissionsMode: "disable" in managed settings; no lower layer can loosen it.

  8. Is ~/.claude.json a setting? No. ~/.claude.json is client state: trust decisions plus user- and local-scope MCP servers, stored by project path. Shared project servers go in .mcp.json. Same server name in two places โ†’ local beats project, project beats user.
    why

    Claude Code keeps ~/.claude.json for itself: folder trust, global preferences and MCP server definitions. It is not a fifth settings.json layer, and there is no project-level claude.json. MCP scope means who a server is for, not where the file lives: user-scope and local-scope (per project path) servers both live in this home file, while project-scope servers your team shares live in .mcp.json at the repo root.

  9. What allowedTools really means In Claude Code, allowedTools means "run without asking" โ€” it is not the full list of tools. To take a tool away, use --tools, disallowedTools or deny. And prefer an allowlist over a disabledTools blocklist. โŒ wrong choice to avoid: a disabledTools blocklist that names only the two destructive tools
    why

    Tools you didn't list still exist; they may just prompt. --tools picks which built-in tools exist, and disallowedTools/deny block a pattern. But careful: in the exam's pipeline scenarios, allowedTools on a subagent or SDK query is treated as the hard set of tools the agent can reach. That holds because the run is unattended: nobody can approve a tool that is not pre-approved, so its call is denied. There, an allowlist beats a disabledTools blocklist, because any tool added to the server later is unreachable by default. A CLAUDE.md sentence is only soft advice, and a PreToolUse hook only reacts after the model already chose the tool.

Skills ยท commands ยท subagents

  1. Where do skills live? Location decides who gets it. A skill in .claude/skills/<name>/SKILL.md is committed, so every clone gets /extract-service; ~/.claude/skills/ is just you. Old-style commands: .claude/commands/<name>.md. Subagents: .claude/agents/<name>.md. โŒ wrong choice to avoid: an inline procedure in the root CLAUDE.md, or ~/.claude/skills/ shared through the team wiki
    why

    Anything under the repo's .claude/ folder is in git, so the skill shows up in every clone and updates arrive with git pull. ~/.claude/skills/ is per user; sharing it through a wiki makes machines drift apart. CLAUDE.md is for always-loaded standards, not an on-demand multi-step procedure, and an MCP server gives tools and data, not prompt workflows. Subagents use the same layout in .claude/agents/.

  2. Who runs a skill? "Model-invoked" means the model picks the skill by matching its description. Then Claude Code loads it and runs the tools that permissions allow. The model never runs scripts itself. Only hooks fire with nobody choosing. Practice Q&A ยท Q42 ยท Practice Q&A ยท Q47 ยท โŒ wrong choice to avoid: "the model chose it and the model ran it", or "the harness fired it on an event"
    why

    There are three actors: you, the model, and the harness (Claude Code, the program around the model). The model reads the skill's description, decides it fits, and asks for it; the harness loads the skill and runs any tool calls the permission rules allow. Typing /name is a second way in. A hook is different: the harness fires it on a lifecycle event, with nobody choosing. Picking a skill is not permission โ€” tool rules still decide what runs.

  3. Who can trigger a skill? Two switches. disable-model-invocation: true โ†’ only you, by typing /name. user-invocable: false โ†’ only the model can pick it. Leave both out โ†’ either way works. Neither one grants tool permissions.
    why

    Each flag turns off one of the two routes. disable-model-invocation: true stops Claude from choosing the skill by itself โ€” right for a deploy skill you never want fired by a casual "go live" sentence. user-invocable: false hides the slash command and leaves the choice to the model. Neither flag touches permissions: the skill still needs tool rules and credentials that allow what it does.

  4. What context: fork does context: fork runs the skill in a separate subagent context, so only a summary comes back. agent: general-purpose on its own line picks who runs it. Leave out context and it runs inline. It is not a git worktree, and not parallel. Practice Q&A ยท Q48 ยท โŒ wrong choice to avoid: thinking fork makes a git worktree, or runs in parallel and merges back
    why

    By default a skill loads inline, so a long analysis skill fills your main conversation and Claude can lose the original task. context: fork sends the body to a subagent with its own context window; the named agent: runs it there and only a summary returns. That is context isolation, nothing more: same working files (no git worktree), not running at the same time as the main session, and no copy of the whole conversation. Splitting the skill or using a smaller model still dumps everything into the main context.

  5. Does allowed-tools restrict? No. allowed-tools: Bash, Read in SKILL.md only pre-approves those tools while the skill runs. It removes nothing, and deny/ask rules still win. argument-hint just shows a hint when you type the command.
    why

    allowed-tools lists tools the skill can use without asking each time. It is a convenience, not a limit: unlisted tools are still available, and a deny or ask rule from any settings layer still beats it. So real read-only behavior comes from permission rules or a subagent's tool list, not from this field. argument-hint is even lighter โ€” it shows a hint in command completion and doesn't check or require anything.

  6. Same prompt, one input? If your team types the same 20-line prompt and only one value changes, make it a slash command with $ARGUMENTS (.claude/commands/release.md, or a skill with /name). Not a hook, not a subagent. Practice Q&A ยท Q11 ยท Practice Q&A ยท Q43 ยท โŒ wrong choice to avoid: a hook, a subagent, or pasting the prompt into the root CLAUDE.md
    why

    A slash command saves a fixed prompt so everyone runs exactly the same text, and $ARGUMENTS fills in the part that changes โ€” a ticket ID, an environment name. It is committed, so the team shares one version with no retyping. A hook is wrong because it fires on lifecycle events, not when you ask; a subagent adds a separate context for what is just a template; pasting it into CLAUDE.md loads release details into every unrelated session. Modern skills can offer the same /name, so it's not only for old command files.

  7. Limit a subagent's tools? Set it where the subagent is defined: frontmatter tools/disallowedTools, or its allowedTools when you create it. A PreToolUse hook on the coordinator can't catch the subagent's calls. โŒ wrong choice to avoid: a coordinator PreToolUse hook that "intercepts" all subagent tool calls
    why

    Each subagent is its own instance with its own config, so its tools are decided where it is defined โ€” the tools list in its .claude/agents/ frontmatter, or allowedTools passed at creation. A web-search worker set up that way can't call write_report, even if a prompt tells it to. A CLAUDE.md list of allowed tools is just advice, and the coordinator's PreToolUse hook never sees calls inside another agent's session. Separate MCP namespaces would work but need server changes for something config already solves.

  8. Main window filling up? If review or exploration is flooding your main conversation, use a read-only subagent with its own context โ€” not /compact more often. If a pipeline analysis is too shallow, use the Explore subagent, not plan mode. Practice Q&A ยท Q13 ยท โŒ wrong choice to avoid: running /compact more often, or switching to plan mode to understand the pipeline
    why

    A subagent starts with a fresh context window: it can read dozens of files, trace callbacks and inheritance, and return only a short list of findings, so the exploration never lands in your main conversation. Compacting more often still lets the material pile up first and may summarize away other context; a bigger model only delays the problem. The built-in Explore subagent is tuned for thorough discovery, which fixes a shallow three-file summary. Plan mode explores too, but inside the main conversation, and it aims at a change plan, not full discovery.

  9. Plan first or just do it? Many files and several valid designs? Start in plan mode, then execute. A one-file fix with a stack trace? Just execute directly. The bigger and less clear the change, the more you plan first. โŒ wrong choice to avoid: starting directly and switching to plan mode only if it gets hard
    why

    Plan mode lets Claude explore the code, map dependencies and propose a design without editing anything, so a split across dozens of files is designed before the first change. Starting directly assumes you already know the right boundaries, and you pay in rework when cross-module dependencies show up halfway. If you already know it's complex, don't wait until it gets hard to switch to plan mode. For a clear, small change, direct execution is the right start.

  10. Three instances, one repo? Give each one its own git worktree on its own branch. fork_session copies the conversation, not the files. Running all three in the same folder on different branches makes them step on each other. โŒ wrong choice to avoid: fork_session or a context: fork skill to run all three inside one session
    why

    git worktree checks out several branches of one repo into separate folders that share one git history. Each Claude Code instance edits its own files, and you merge with normal branch operations. Three separate clones would work but leave histories to reconcile by hand. One shared folder fails because switching branches changes the files under the other instances. fork_session and a context: fork skill make separate conversations, not separate filesystems, so they can't stop three big edits from colliding.

Hooks

  1. Where do hooks go? Hooks are settings, not text. Register them under a top-level hooks key in .claude/settings.json (or local, user or managed settings), never in CLAUDE.md. The script itself lives elsewhere, like .claude/hooks/after_edit.py.
    why

    Hooks can go in .claude/settings.json (shared), .claude/settings.local.json (personal), ~/.claude/settings.json (user) or managed settings, and Claude Code reads them at startup. Writing "always run the formatter" in CLAUDE.md registers nothing โ€” the model may or may not follow it. The JSON only points to a script; the logic lives in that script, which must exist and follow the hook's input/output contract.

  2. How to read a hook entry Read it outside in: "PostToolUse": [{"matcher": "Write|Edit", "hooks": [{"type": "command", "command": "โ€ฆ"}]}] = event โ†’ matcher on the tool name โ†’ handlers. $CLAUDE_PROJECT_DIR points to the repo. Event details arrive as JSON on stdin.
    why

    The event name (PostToolUse) decides when it fires. Each matcher is a regex on the tool name, so Write|Edit covers both editing tools. The inner hooks list holds the handlers, and type: command tells Claude Code to run a shell command. $CLAUDE_PROJECT_DIR expands to the repo root, so scripts work from anywhere. The handler gets the tool name, inputs and file paths as JSON on stdin โ€” don't expect a $FILE variable.

  3. Format after every edit? Use a PostToolUse hook matching Write|Edit that runs the formatter. A CLAUDE.md sentence or a /format command depends on someone remembering; the hook doesn't. Practice Q&A ยท Q9 ยท Practice Q&A ยท Q44 ยท โŒ wrong choice to avoid: adding "always run the formatter" to CLAUDE.md, or a manual /format command
    why

    The hook ties the formatter to the event: after any Write or Edit succeeds, Claude Code runs it on the file, whether the model remembered or not. A CLAUDE.md rule needs the model to comply on every single edit; a skill runs only when formatting seems relevant; /format relies on a human. The hook still misses edits made through Bash or outside Claude Code, so keep a CI formatting check as the repo-wide backstop.

  4. Before or after the call? PreToolUse runs before the call, and exit code 2 blocks it and sends stderr back to Claude as the reason. PostToolUse runs after, so it can't undo the write.
    why

    Timing is the whole difference. A PreToolUse hook sees the proposed call before it runs; exit code 2 vetoes it, and the model can't talk its way around that. A PostToolUse hook runs after the tool succeeded: it can format, test or log, but the file is already on disk. Both are run by the client, so unlike CLAUDE.md text, they hold even when the model would rather do something else.

  5. Format in PreToolUse? You can't โ€” when a PreToolUse hook on Write fires, no file exists yet. Use PreToolUse to check or block inputs, and PostToolUse to fix outputs. โŒ wrong choice to avoid: a PreToolUse hook on Write that formats the content before the file is written
    why

    At PreToolUse time the Write tool hasn't run, so there is nothing on disk for Checkstyle or Prettier to format; the hook only sees the proposed input. That makes it the place to inspect or reject โ€” block a write to a protected path, lint a shell command โ€” not to fix output. PostToolUse gets the finished write and can correct it in place. A hook on Read that checks files before they enter context is on the wrong event entirely.

  6. Save transcript before compaction? Use PreCompact. It fires before /compact and before auto-compaction. There is no Compact tool for PreToolUse to match, and a PostToolUse hook on every tool is just noise. โŒ wrong choice to avoid: a PostToolUse hook on every tool that appends each result to a log
    why

    Compaction summarizes the conversation and throws away detail, so anything you want must be saved just before. PreCompact is the event for that: it fires before manual and automatic compaction and gets the transcript path, so it can copy it to a timestamped log. Compaction is not a tool the model calls, so no PreToolUse matcher catches it. Logging every result with PostToolUse makes huge files that still miss reasoning and user messages, and a hook on Write fires at unrelated times.

  7. Which event for which job? UserPromptSubmit adds context or checks the prompt when the user sends it. Stop/SubagentStop enforce "done" criteria. SessionStart loads saved state.
    why

    Match each event to a job. UserPromptSubmit runs when the user sends a message, before the model sees it. Stop and SubagentStop run when Claude or a subagent finishes โ€” the place to check that tests passed or the checklist is done, or to send a notification. SessionStart runs once when a session opens. Together with PreToolUse, PostToolUse and PreCompact, these cover the whole session.

  8. What can a hook miss? A Write|Edit matcher misses edits made through Bash or an outside editor, so back it with a required CI check. And a PreToolUse gate can deny or ask, but it can't wait hours for approval. โŒ wrong choice to avoid: a PreToolUse hook that pauses and waits for a human approval signal
    why

    A hook fires only for the tools its matcher names. A sed command through Bash, a script that rewrites files, or a developer in their IDE never trigger a Write|Edit hook, so the check is silently skipped. The repo-wide guarantee is a required CI check on every pull request, however the change was made. Hooks are also synchronous: a PreToolUse hook can block or ask the user, but it can't pause for an outside approval, so human sign-off belongs in a queue the pipeline routes to.

Headless, CI & the Agent SDK

  1. Does -p limit Claude? No. claude -p "โ€ฆ" just runs one prompt without the interactive screen, and --output-format json|stream-json makes the output parseable. -p does not restrict file access. If a headless check is shallow, blame the vague prompt, not the flag. โŒ wrong choice to avoid: thinking -p blocks file reads, or that --output-format json breaks the results
    why

    -p (print mode) runs one prompt and exits โ€” what CI scripts need โ€” and --output-format json or stream-json wraps the result for a pipeline to parse. Neither changes what Claude Code can do: it still has Read, Grep and Glob, and the format doesn't touch the analysis. If a headless check claims 100% coverage that audits disprove, the prompt had no validation criteria, so the model pattern-matched instead of checking each endpoint. Give explicit criteria and an expected output format; a second instance reviewing the same vague output won't help.

  2. Safe CI setup Stack four controls: headless mode + --allowedTools "Bash(npm run test:*)" + hooks + read-only credentials. --allowedTools only adds pre-approval and never beats a deny. --tools picks built-in tools and doesn't filter MCP tools.
    why

    Headless mode makes the run scriptable. --allowedTools "Bash(npm run test:*)" pre-approves exactly the commands the job needs, so it never stalls on a prompt. Hooks add fixed checks around tool calls. Read-only credentials mean even a hijacked prompt can't push or publish. An allow never cancels a deny from any settings layer. --tools is a different knob: it picks which built-in tools exist and doesn't affect MCP tools, which are set up and denied separately.

  3. What loads in CI? The runner checks out the repo, so the project CLAUDE.md, .claude/settings.json, rules and skills all load. But ~/.claude/CLAUDE.md doesn't exist there โ€” nobody put it on that machine. Practice Q&A ยท Q50 ยท โŒ wrong choice to avoid: "headless mode turns off memory", or "managed policy strips user memory in CI"
    why

    A GitHub Actions runner is a fresh machine with only what the workflow checks out or sets up. Everything committed โ€” CLAUDE.md, .claude/rules/, .claude/settings.json, .claude/skills/ โ€” is there and loads normally. A developer's home-folder memory file was never copied, so rules that lived only there vanish in CI. Headless mode doesn't turn off memory, and no managed policy strips user files; the file just isn't on the runner.

  4. @claude did nothing? That's the safety gate working. The @claude Action first checks that the commenter has write access. An outside contributor's comment triggers nothing, on purpose, to block prompt injection. It is not a missing API key. Practice Q&A ยท Q49 ยท โŒ wrong choice to avoid: "the API key is missing from the fork", or "the workflow lacks the comment event"
    why

    Anyone can comment on a public repo, so a workflow that ran on every @claude mention would let strangers steer an agent that holds the repo's credentials. So the official Action checks the commenter's permission level and runs only for users with write access. The API key lives in the repo's secrets, not in the contributor's fork, and a missing event subscription wouldn't explain why trusted and untrusted commenters behave differently.

  5. What the workflow YAML controls Only three things: WHEN it runs (events + @claude), WHAT it may touch (permissions:), and WHICH secret (ANTHROPIC_API_KEY). .github/workflows/claude.yml teaches no conventions โ€” those all come from the committed files in the repo.
    why

    The workflow file is plumbing, not policy. The on: block and the @claude trigger decide when the job runs, permissions: limits the GitHub token, and a secret reference supplies the key. Coding standards, permission rules, hooks and skills all come from the committed CLAUDE.md, .claude/rules/, .claude/settings.json and .claude/skills/. On GitHub those committed files are the only voice; there is no user layer to fill gaps.

  6. What may block a merge? Only deterministic checks, like tests and lint, should block a merge. AI review is advice. And adding a workflow file doesn't create a gate by itself โ€” branch protection must mark the check as required. Practice Q&A ยท Q14 ยท โŒ wrong choice to avoid: letting Claude's opinion on code quality, or any AI output, block the merge
    why

    Tests and linters give the same answer every run, so a failure is a repeatable fact the team agreed to enforce. An AI reviewer's opinion is useful, but it isn't proven reliable or objective enough to be a hard gate, so post it as comments. Making any AI output blocking lets unsupported claims stop merges; removing every gate lets known test failures through. And a workflow that runs checks is not a gate until branch protection lists it as required.

  7. Can deny stop the network? Not fully. deny: ["Bash(git push *)"] can't stop an allowed script from pushing. In CI you need read-only tokens (permissions: contents: read, persist-credentials: false) plus a sandbox that blocks outgoing network traffic. Practice Q&A ยท Q12 ยท โŒ wrong choice to avoid: a deny rule on git push or curl alone, or prompt instructions
    why

    Permission rules match the command Claude Code is asked to run. So a deny on git push or curl does nothing when an allowed script like npm test opens a connection or pushes by itself. The guarantee must come from the environment: a token with permissions: contents: read and persist-credentials: false, so no write credential is on the runner, plus a network sandbox or egress (outgoing traffic) policy. Prompt instructions and trusting the model restrict nothing when the model makes a mistake or reads malicious input.

  8. SDK or plain API? The Agent SDK's query({ prompt, options }) takes tools, allowedTools, disallowedTools, mcpServers, and loads CLAUDE.md based on setting_sources/cwd. A plain messages.create call never finds local files. โŒ wrong choice to avoid: thinking a plain Messages API call finds CLAUDE.md, or an undocumented --append-config flag
    why

    The Agent SDK is the Claude Code runtime as a library, not a thin wrapper on the Messages API. A query() call takes the same kinds of options Claude Code uses and, depending on cwd and setting_sources, reads the project's CLAUDE.md and settings; an empty setting_sources turns that off. A direct anthropic.messages.create call sends only what you put in the request โ€” it has no idea a CLAUDE.md exists on disk. So when a question names the runtime, check which one before assuming instructions get discovered.

Review sources

Domain 2 lesson and worked explanations ยท Official reference↗

Fact wording rewritten as concrete numbered facts on 25 September 2026. The mock providers' original keys are preserved on their review pages; these recall lines retain the engineering caveats.