Study guides / CCDV-F / Domain 7

Claude Code · Lesson 1 of 2

7.1 - Operating Claude Code Day to Day

Configure project conventions with CLAUDE.md, control tool permissions, wire up hooks and MCP servers, and use slash commands so Claude Code applies your team's standards without you repeating them every session.

A CLAUDE.md file in a project root gives Claude Code persistent context about that codebase — conventions, commands to run, things to avoid — without you re-explaining them at the start of every session. It is read automatically at session start; the discipline is keeping it accurate and current, since stale guidance in it is worse than no guidance at all (Claude will confidently follow instructions that no longer match the codebase). Treat it like any other reviewed file: update it in the same commit that changes the convention it documents.

The CLAUDE.md hierarchy

CLAUDE.md is not a single file — it resolves across a hierarchy, and knowing which level to put something in matters. From broadest to narrowest: an enterprise managed policy file (deployed by an organization's IT/security team, outside any individual repo, and not overridable by project or user files); a project-level CLAUDE.md at the repo root, checked into version control and shared with the whole team (build commands, architectural conventions, style rules everyone needs); and a user-level CLAUDE.md at ~/.claude/CLAUDE.md, which applies across every project for that individual and is where personal preferences belong (e.g. "prefer concise diffs" or an individual's tooling shortcuts) — it should never carry team-wide conventions, since teammates never see it. CLAUDE.md files also support @path/to/file imports, letting a root file pull in more detailed docs (e.g. a testing guide) without bloating the main file that loads on every session.

Key concept

Match the scope of the guidance to the scope of the file: team-wide, repo-specific conventions go in the project CLAUDE.md that gets committed; individual preferences that would be noise to teammates belong in the user-level file instead. Mixing the two is a common source of a bloated, half-relevant project file.

The permission system: allow, ask, deny

Claude Code gates tool use through a permission system configured in settings.json, not through CLAUDE.md prose. Rules are written as Tool(pattern) strings — for example Bash(git commit:*), Bash(rm:*), or Edit — and each rule is placed in one of three lists: allow (run without prompting), ask (prompt the user before running), or deny (block outright, no prompt). Settings themselves live at multiple scopes — a checked-in .claude/settings.json shared with the team, a gitignored .claude/settings.local.json for an individual's personal overrides, a user-level ~/.claude/settings.json, and an enterprise-managed settings file the organization controls — and higher scopes generally take precedence over lower ones, with enterprise managed policy always winning. But the rule that actually matters for the exam is orthogonal to scope: within permission evaluation, a matching deny rule always wins over a matching allow rule, regardless of which file each came from. You cannot allow-list your way past a deny.

Common exam distractor

A scenario states a command matches both an allow rule in one settings file and a deny rule in another, then asks what happens. The trap answer picks whichever file has narrower/closer scope, or whichever was "added most recently." The correct rule is simpler and file-independent: deny beats allow whenever both match, full stop.

Slash commands and starting fresh

Claude Code ships built-in slash commands for session control — /clear wipes context and starts a genuinely fresh session, /compact summarizes and shrinks the current conversation instead of discarding it, /permissions and /mcp inspect the permission and MCP configuration currently in effect, and /agents manages configured subagents. Beyond the built-ins, custom slash commands package a repeated prompt or workflow into a short, memorable invocation: a Markdown file under .claude/commands/ (project-scoped, shared) or ~/.claude/commands/ (personal) becomes a command named after the file, its body is the prompt template, and $ARGUMENTS in that body is substituted with whatever text follows the command when invoked. Frontmatter on the file can restrict which tools the command is allowed to use and describe it for /help. Subdirectories namespace commands (a file at .claude/commands/git/commit.md becomes /git:commit).

Just as important day to day as building commands is knowing when not to reuse a session: /clear starts fresh when you are switching to an unrelated task, rather than letting stale context from the last task bleed into the next one's reasoning (the same principle Lesson 3.4 covers for API-level session management). /compact is the right tool instead when the task is still the same one but the conversation has grown long and is approaching the context window — you want to keep the thread of work, just condensed.

Hooks: deterministic control at tool boundaries

CLAUDE.md and prompts are probabilistic guidance — Claude usually follows them, but "usually" is not a guarantee. When a rule must hold every time (block a dangerous command outright, log every file edit, enforce a lint pass before a commit succeeds), Claude Code's hooks configured in settings.json are the deterministic mechanism, mirroring the same hooks-versus-prompts tradeoff from the Agent SDK (Lesson 1.5) but applied to the CLI tool itself. Hooks bind a shell command to a lifecycle event — PreToolUse fires before a tool runs and can block it, PostToolUse fires after and can react to the result, UserPromptSubmit fires when the user submits a prompt, and Stop fires when Claude finishes responding — optionally filtered to specific tools via a matcher pattern. The hook script receives event details as JSON on stdin; exit code 0 lets execution continue normally, while exit code 2 blocks the action and feeds the hook's stderr back to Claude as the reason, so Claude can adjust and retry rather than the operation silently failing.

MCP server configuration

External tools and data sources are wired in through the Model Context Protocol. A project-scoped .mcp.json at the repo root, checked into version control, gives every teammate the same configured servers automatically when they open the project. The claude mcp add CLI command registers a server at a chosen scope — local (this machine only, not shared), project (writes to .mcp.json), or user (available across all of that person's projects) — and /mcp inside a session lists configured servers along with their connection and authentication status, which is the first place to check when a tool a server should provide is not showing up.

Exam traps

Practice question

A developer finishes a large refactor with Claude Code and immediately starts asking about an unrelated bug in a different part of the codebase, in the same session. What's the recommended practice?

  • A Continue in the same session - more accumulated context is always beneficial.

    Irrelevant prior-task context can distract from or bias reasoning about the new, unrelated task rather than helping it.

  • B Run /clear to start a fresh session before describing the unrelated bug. Correct

    This avoids carrying irrelevant refactor context into an unrelated task, keeping the new session focused on just what's relevant now.

  • C Rewrite CLAUDE.md to describe the new bug before continuing.

    CLAUDE.md is for standing project context, not a per-task description - this misuses the file for something a fresh session handles correctly.

  • D Create a new slash command for the bug fix.

    A slash command packages a repeatable action; a one-off unrelated bug investigation isn't what it's for.

Build exercise: Write a CLAUDE.md, lock down permissions, and add a hook and a slash command

Beginner · 35 minutes

You'll practice:

  1. In a real project, write a project-level CLAUDE.md covering the test command, one important convention, and one thing to avoid - keeping any personal preferences out of it.

    A short, accurate file scoped correctly is more useful than a long, stale, or over-broad one - practicing both concision and correct scope matters here.

    You should see: Claude Code correctly following that guidance in a new session without being told again.

    Hints
    1. What belongs in a file every teammate will read versus a file only you will read?
    2. Put repo-wide facts (build/test commands, shared conventions) in the project CLAUDE.md at the repo root. Anything personal to you belongs in ~/.claude/CLAUDE.md instead, not this file.
    3. # Project conventions
      
      ## Commands
      - Run tests: `npm test`
      - Lint: `npm run lint`
      
      ## Conventions
      - All API routes live under `src/routes/` and export a default handler.
      
      ## Avoid
      - Do not edit files under `generated/` - they are build output.
  2. In .claude/settings.json, add an allow rule for a safe read-only command, an ask rule for a moderately risky command, and a deny rule for a destructive one.

    This is the mechanism that actually gives you deterministic control over what Claude Code can do unattended - CLAUDE.md prose can't block a command, only permission rules and hooks can.

    You should see: Claude Code runs the allowed command silently, prompts you before the ask-listed one, and refuses the denied one outright even if you approve it interactively.

    Hints
    1. What's the Tool(pattern) syntax Claude Code uses for a permission rule, and which three buckets can a rule go in?
    2. Rules look like "Bash(git commit:*)" and are placed under an "allow", "ask", or "deny" array inside the "permissions" key of settings.json.
    3. {
        "permissions": {
          "allow": ["Bash(git status)", "Bash(git diff:*)"],
          "ask": ["Bash(git commit:*)"],
          "deny": ["Bash(rm -rf:*)"]
        }
      }
  3. Create a custom slash command at .claude/commands/review.md that takes a file path via $ARGUMENTS and asks Claude to review it against your project's conventions.

    This turns a prompt you'd otherwise retype into a one-word, shareable invocation, and demonstrates how $ARGUMENTS threads user input into the template.

    You should see: Typing /review src/routes/users.js triggers the full review prompt with that path substituted in, without you typing the rest by hand.

    Hints
    1. What placeholder does Claude Code substitute with whatever text follows the slash command name?
    2. Write a Markdown file whose body is the prompt template; anywhere you write $ARGUMENTS, the text typed after the command name gets inserted.
    3. ---
      description: Review a file against project conventions
      ---
      Review $ARGUMENTS against the conventions in CLAUDE.md. Flag any violations and suggest fixes.
  4. Add a PreToolUse hook in settings.json that blocks any Bash call matching a force-push pattern, returning exit code 2 with an explanatory message.

    This is the deterministic-enforcement mechanism from this lesson in action - unlike CLAUDE.md guidance, a PreToolUse hook that exits 2 physically prevents the command from running, every time.

    You should see: Attempting a force push is blocked before it executes, and Claude sees your hook's stderr message as the reason and adjusts its next action instead of retrying blindly.

    Hints
    1. Which hook event fires before a tool runs, and what exit code tells Claude Code to block the action rather than allow it?
    2. Configure a PreToolUse hook matched to the Bash tool that runs a small script; the script inspects the command being run and, if it matches a force-push pattern, writes an explanation to stderr and exits with code 2.
    3. {
        "hooks": {
          "PreToolUse": [
            {
              "matcher": "Bash",
              "hooks": [{ "type": "command", "command": "scripts/block-force-push.sh" }]
            }
          ]
        }
      }
      
      # scripts/block-force-push.sh
      #!/bin/sh
      read -r input
      echo "$input" | grep -q -- '--force' && { echo 'Force push is blocked by policy' >&2; exit 2; }
      exit 0

Sources