On this page:
4.1 Learning Objectives
4.2 Motivation:   Configuration as Code
4.2.1 Why a lecture about files?
4.2.2 Intuition:   three questions, three kinds of files
4.2.3 The map
4.3 Running Example:   Terp  Tasks Gets a Team
4.4 The Scope Model and Precedence
4.5 Context Files:   CLAUDE.md
4.6 Policy Files:   the settings.json Family
4.6.1 .claude/  settings.json—the team’s rules
4.6.2 .claude/  settings.local.json—your exceptions
4.6.3 ~/  .claude/  settings.json—your defaults everywhere
4.6.4 Which layer? A decision procedure
4.7 Capability Files I:   .mcp.json
4.8 The State File:   ~/  .claude.json
4.9 Capability Files II:   commands/  , agents/  , skills/  , hooks/
4.9.1 .claude/  commands/—reusable prompts (you trigger)
4.9.2 .claude/  agents/—subagents (the model delegates)
4.9.3 .claude/  skills/—packaged know-how (the model matches)
4.9.4 Hooks—deterministic automation (the harness triggers)
4.10 Git  Hub-Ready Mini-Project:   the Terp  Tasks Configuration Kit
4.10.1 Structure
4.10.2 The complete settings.json
4.10.3 Verifying the kit (the “tests” of a config project)
4.11 Practice Exercises
4.11.1 Basic
4.12 Summary
4.12.1 Key takeaways
4.12.2 Terminology
4.12.3 Common mistakes
4.12.4 Connections
9.1

4 The Important Files of Claude Code🔗

    4.1 Learning Objectives

    4.2 Motivation: Configuration as Code

      4.2.1 Why a lecture about files?

      4.2.2 Intuition: three questions, three kinds of files

      4.2.3 The map

    4.3 Running Example: TerpTasks Gets a Team

    4.4 The Scope Model and Precedence

    4.5 Context Files: CLAUDE.md

    4.6 Policy Files: the settings.json Family

      4.6.1 .claude/settings.json—the team’s rules

      4.6.2 .claude/settings.local.json—your exceptions

      4.6.3 ~/.claude/settings.json—your defaults everywhere

      4.6.4 Which layer? A decision procedure

    4.7 Capability Files I: .mcp.json

    4.8 The State File: ~/.claude.json

    4.9 Capability Files II: commands/, agents/, skills/, hooks/

      4.9.1 .claude/commands/—reusable prompts (you trigger)

      4.9.2 .claude/agents/—subagents (the model delegates)

      4.9.3 .claude/skills/—packaged know-how (the model matches)

      4.9.4 Hooks—deterministic automation (the harness triggers)

    4.10 GitHub-Ready Mini-Project: the TerpTasks Configuration Kit

      4.10.1 Structure

      4.10.2 The complete settings.json

      4.10.3 Verifying the kit (the “tests” of a config project)

    4.11 Practice Exercises

      4.11.1 Basic

    4.12 Summary

      4.12.1 Key takeaways

      4.12.2 Terminology

      4.12.3 Common mistakes

      4.12.4 Connections

4.1 Learning Objectives🔗

By the end of this lecture you should be able to:

  1. Name the key files and directories Claude Code reads (CLAUDE.md, .claude/settings.json, .claude/settings.local.json, ~/.claude/settings.json, .mcp.json, ~/.claude.json, and the .claude/{agents,skills,commands,hooks}/ directories) and state each one’s purpose in a sentence.

  2. Explain the three configuration scopes—user, project, and local—and predict which setting wins when they conflict, using the precedence chain.

  3. Write permission rules (allow / ask / deny) in settings.json and trace how a specific tool call is evaluated against them.

  4. Decide which file a given piece of configuration belongs in, and justify the decision in terms of audience (me vs. my team vs. the agent) and durability.

  5. Create a custom slash command, a subagent, and a hook as files, and explain how each differs in who decides it runs (you, the model, or the harness).

  6. Distinguish user-editable configuration from tool state (~/.claude.json) and explain why the latter stays out of version control and out of your editor.

  7. Assemble a complete, committable .claude/ configuration kit for a real repository.

4.2 Motivation: Configuration as Code🔗

4.2.1 Why a lecture about files?🔗

In the last lecture you learned to operate Claude Code: workflows, prompting, context management. But everything you typed there was ephemeral—it vanished at /clear. The durable half of the skill is configuration as code: a small set of files, mostly living in your repository, that shape every session before you type a single prompt.

4.2.2 Intuition: three questions, three kinds of files🔗

Every file we meet today answers one of three questions:

"What should the agent KNOW?"     -->  context files      (CLAUDE.md)

"What MAY the agent DO?"          -->  policy files       (settings*.json, hooks)

"What CAN the agent USE?"         -->  capability files   (.mcp.json, agents/, skills/, commands/)

Keep this triad in mind; when you’re unsure where something goes, ask which question it answers.

4.2.3 The map🔗

An annotated view of everything in play, with the two roots—your home directory and your repository:

~ (your machine -- follows YOU)

|- .claude/

|  |- settings.json    <- your global defaults

|  |- CLAUDE.md        <- personal context

|  |- agents/          <- personal agents

|  |- skills/          <- personal skills

|  +- commands/        <- personal commands

+- .claude.json        <- tool STATE:

   auth, caches, trust    (not yours to edit!)

   per-project state

 

REPO (the project -- follows the TEAM)

terptasks/

|- CLAUDE.md              <- project context

|- .mcp.json              <- shared MCP servers

|- .claude/

|  |- settings.json       <- shared policy (committed)

|  |- settings.local.json <- personal overrides (git-ignored)

|  |- agents/             <- project subagents

|  |- skills/             <- project skills

|  |- commands/           <- project slash commands

|  +- hooks/              <- scripts run by the harness

+- src/ ...

Priority guide from the reference handout, which we’ll follow:

File

Purpose

Scope

CLAUDE.md

persistent instructions about the project

project (or user)

.claude/settings.json

shared config: permissions, hooks, env

project

.claude/settings.local.json

personal settings, not committed

local

~/.claude/settings.json

global defaults for all projects

user

.mcp.json

project MCP server definitions

project

~/.claude.json

tool state: auth, MCP, trust, caches

user

.claude/agents/

custom subagents

project/user

.claude/skills/

reusable skills

project/user

.claude/commands/

custom slash commands

project/user

.claude/hooks/

scripts run around tool execution

project/user

4.3 Running Example: TerpTasks Gets a Team🔗

Last lecture, TerpTasks was yours alone; you wrote its CLAUDE.md and fixed the sorting bug. The story continues: two teammates are joining, and the project is moving to a shared GitHub repo. Overnight, questions appear that never mattered solo:

  1. Alice auto-approved pytest on her machine—how does Bob get that without clicking “allow” forty times? → shared settings.json

  2. Bob likes verbose model output; Alice hates it. → settings.local.json

  3. Nobody, ever, should let the agent read .env (it now holds a database password). → deny permission, committed

  4. The team wants the agent to file GitHub issues. → .mcp.json

  5. Every PR should get the same style of first-pass review. → a shared subagent

By the end of the lecture, we will have built the complete configuration kit that answers all five—the mini-project in GitHub-Ready Mini-Project: the TerpTasks Configuration Kit.

4.4 The Scope Model and Precedence🔗

Before individual files, the one concept that organizes them all.

Every Claude Code setting exists at a scope: user (~/.claude/, applies to all your projects), project (checked into the repo, applies to everyone who clones it), or local (in the repo but git-ignored, applies to you in this repo only). When scopes conflict, the more specific scope wins; deny rules win regardless.

In plain English: global defaults for you everywhere; committed files for everyone here; local files for you, here.

Precedence chain (highest wins):

enterprise managed policy        (IT department; you can't override)

       | overrides

command-line flags               (this invocation only)

       | overrides

.claude/settings.local.json      (you, this project)

       | overrides

.claude/settings.json            (the team, this project)

       | overrides

~/.claude/settings.json          (you, everywhere)

Intuition. The setting closest to the work wins. The enterprise layer at top is new but sensible: your employer’s security team gets the final word, which is exactly what makes companies willing to allow these tools at all.

Misconception. “Project settings override my local ones—the repo is authoritative.” Backwards for settings: local beats project, because personal ergonomics shouldn’t require editing shared files. (Exception: a deny rule at any level cannot be un-denied below the enterprise level—security is one-directional.)

Worked example. Alice’s three files contain:

~/.claude/settings.json:          { "permissions": { "allow": ["Bash(git status)"] } }

terptasks/.claude/settings.json:  { "permissions": { "allow": ["Bash(pytest -q)"],

                                                     "deny":  ["Read(.env)"] } }

terptasks/.claude/settings.local.json:

                                  { "permissions": { "allow": ["Bash(python -m terptasks.cli *)"] } }

Trace four tool calls the agent attempts:

Attempted call

Matching rule

Source

Result

Bash(git status)

allow

user scope

runs without prompting

Bash(pytest -q)

allow

project scope

runs without prompting

Bash(python -m terptasks.cli list)

allow (wildcard)

local scope

runs -- only for Alice

Read(.env)

deny

project scope

blocked, even with a local allow

The union of allows applies; deny trumps everything. Bob clones the repo and immediately inherits rows 2 and 4—that’s configuration as code paying off.

4.5 Context Files: CLAUDE.md🔗

CLAUDE.md is a Markdown file automatically loaded into the model’s context at session start. It exists at multiple scopes—~/.claude/CLAUDE.md (user), <repo>/CLAUDE.md (project)—and all applicable ones load together (they stack; they don’t override each other, unlike settings).

In plain English: the project’s AI playbook: architecture, commands, conventions, things to avoid. We covered how to write one last lecture; today we place it in the file ecosystem.

Intuition. CLAUDE.md is a context file—it answers “what should the agent know?” It has no enforcement power: writing “never edit .env” in CLAUDE.md is a polite request the model almost always honors; a deny rule in settings.json is physics. Know which one you need.

Common misconceptions.

  • “CLAUDE.md and settings.json overlap.” They never do: prose the model reads vs. JSON the harness enforces. Advice vs. law.

  • “One CLAUDE.md per machine.” They stack: your ~/.claude/CLAUDE.md (“I prefer type hints”) loads alongside each project’s. Keep personal style user-scoped; keep project facts project-scoped.

Worked example—the stack in action. Alice’s ~/.claude/CLAUDE.md says “explain changes before making them.” TerpTasks’ CLAUDE.md (from last lecture) says “all datetimes are timezone-aware UTC.” In a TerpTasks session, both apply. When Bob works in the same repo, only the UTC rule applies to him—he never sees Alice’s personal preference. Facts about the project went in the repo; facts about a person stayed in ~.

Two features worth knowing:

  • Imports: a line like @docs/architecture.md inside CLAUDE.md pulls another file into context—keep CLAUDE.md short and link out.

  • Subdirectory files: a CLAUDE.md inside tests/ loads when the agent works on files there—put test-specific conventions next to the tests.

4.6 Policy Files: the settings.json Family🔗

4.6.1 .claude/settings.json—the team’s rules🔗

A JSON file, committed to the repository, that configures harness behavior for every session in this project: tool permissions, environment variables, hooks, model choice, and other options.

In plain English: “What is Claude allowed to do here, for everyone on the team?”

TerpTasks’ shared policy (answering questions 1 and 3 from the running example):

{
  "permissions": {
    "allow": [
      "Bash(pytest -q)",
      "Bash(pytest tests/*)",
      "Bash(ruff check:*)",
      "Bash(ruff format:*)",
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)"
    ],
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Bash(git push:*)"
    ]
  },
  "env": {
    "TERPTASKS_ENV": "development"
  }
}

Reading permission rules. Each rule is Tool or Tool(specifier). For Bash, the specifier matches the command: Bash(git diff:*) allows git diff with any arguments; bare Bash(git status) allows exactly that command. For file tools, the specifier is a path pattern: Read(.env) matches the project’s .env.

Step-by-step trace. The agent wants to run git push origin main:

  1. Harness checks deny first → Bash(git push:*) matches → blocked, with the reason shown to the model.

  2. The model sees the denial and must adapt (typically: it asks you to push).

Now ruff check src: deny list → no match; allow list → Bash(ruff check:*) matches → runs with no prompt. A command matching neither list falls back to the default: ask the human. So the three verdicts are allow / ask / deny, and “ask” is the safety net for everything you didn’t anticipate.

Why deny git push? The team decided pushes are human-only actions This is that value, encoded as policy instead of vigilance.

Misconception. “Allow rules are dangerous.” An overly-broad allow (Bash(*)) is dangerous. Precise allows are the opposite: they remove prompt fatigue for known-safe commands so your attention is saved for the calls that genuinely need review. Prompt fatigue—clicking “yes” reflexively—is itself a security failure mode.

4.6.2 .claude/settings.local.json—your exceptions🔗

Same schema, different audience: this file is for you in this repo, and Claude Code adds it to .gitignore automatically when it creates it.

Worked example. Bob is experimenting with the SQLite migration and got tired of approving sqlite3 calls:

{
  "permissions": {
    "allow": ["Bash(sqlite3 dev.db *)"]
  }
}

This belongs local, not shared, because the team hasn’t adopted SQLite—it’s Bob’s experiment. If the migration lands, the rule graduates into settings.json via a normal reviewed PR. That graduation path—local experiment → committed policy—is the intended workflow.

4.6.3 ~/.claude/settings.json—your defaults everywhere🔗

Same schema again, applying to all your projects. Good candidates: "Bash(git status)", "Bash(ls:*)"commands safe in any repo—plus personal defaults like your preferred model. Bad candidates: anything project-specific (a path pattern that means something different in another repo could surprise you).

4.6.4 Which layer? A decision procedure🔗

Is it a fact/instruction in prose?  ------------> CLAUDE.md (project or user)

Is it enforcement (permissions/hooks/env)?

    |

    |- Should teammates inherit it? -----------> .claude/settings.json   (commit)

    |- Just me, just this repo?     -----------> .claude/settings.local.json

    +- Me, every repo?              -----------> ~/.claude/settings.json

4.7 Capability Files I: .mcp.json🔗

.mcp.json, at the repository root and typically committed, declares project-scoped MCP serversexternal tool providers (databases, GitHub, browsers) the agent may connect to, each specified by a launch command or URL.

In plain English: “What external tools can Claude use in this project?” In the MCP lecture: servers expose tools over a standard protocol; Claude Code is a client. This file is where a project pins its servers so the whole team shares them.

TerpTasks wants the agent to file GitHub issues (question 4 from the running example):

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

Three teaching points in ten lines:

  1. ${GITHUB_TOKEN}environment-variable expansion. The reference to a secret is committed; the secret itself lives in each developer’s shell. Never inline tokens in this file—it’s in Git.

  2. Trust prompt. The first time you open a project with an .mcp.json, Claude Code asks whether you trust its servers—because a malicious repo could otherwise hand your agent malicious tools the moment you open it. That approval is recorded per-project in ~/.claude.json (The State File: ~/.claude.json).

  3. Scope, again. MCP servers can also be configured user-wide (claude mcp add scope user) for tools you want everywhere; .mcp.json is specifically the shared project scope.

Misconception. “MCP config goes in settings.json.” Separate file, separate concern: settings.json is what the agent may do; .mcp.json is what it can reach. (History note: user-scoped MCP servers actually live in ~/.claude.json, which is one reason that file exists.)

4.8 The State File: ~/.claude.json🔗

A JSON file in your home directory where Claude Code persists its own state: authentication/session data, user-scoped MCP servers, per-project trust decisions and permission history, and caches.

In plain English: the tool’s memory about you and your machinenot configuration you author.

Intuition. Every serious CLI has one of these (~/.gitconfig is half config, half state; ~/.docker/config.json stores auth). The design rule it teaches: separate what humans author from what machines persist. Human-authored files are small, diffable, committable. State files are none of those.

Practical implications.

  • Don’t hand-edit it; use commands (claude mcp add, /login) that write it for you.

  • Never commit it or paste it into an issue—it contains credentials.

  • Debugging tip: if a project stopped prompting for trust, or an old permission decision haunts you, this is where that state lives.

Misconception. ~/.claude.json vs ~/.claude/settings.jsonsame thing.” The path difference matters: settings.json is yours to edit (config); .claude.json is the tool’s (state). If you find yourself editing the latter, you almost certainly want the former.

4.9 Capability Files II: commands/, agents/, skills/, hooks/🔗

Four directories, one organizing question: who decides this runs?

Directory

Contains

Who triggers it

.claude/commands/

prompt templates

you (type /name)

.claude/agents/

subagent definitions

the model (delegates) or you

.claude/skills/

skills (instructions + resources)

the model (matches your request)

.claude/hooks/

shell scripts

the harness (deterministic, on events)

Each exists at project scope (committed, team-shared) and user scope (~/.claude/..., personal). We’ll build one of each for TerpTasks.

4.9.1 .claude/commands/—reusable prompts (you trigger)🔗

A Markdown file per command; the body is the prompt, optional YAML frontmatter adds metadata, and $ARGUMENTS interpolates what you type after the command name. Last lecture’s /release-check was one. A second example, fix-issue.md:

---
description: Fix a GitHub issue end-to-end
argument-hint: <issue-number>
---
Fix GitHub issue #$ARGUMENTS in this repository:
1. Read the issue with the GitHub MCP tools.
2. Reproduce the problem with a failing test.
3. Fix it; run `pytest -q` until green.
4. Summarize the change and reference the issue number.

Now /fix-issue 42 expands to the full checklist with 42 substituted. Note how it composes with earlier files: step 1 works because .mcp.json provides GitHub tools; step 3’s pytest runs unprompted because settings.json allows it. The kit is a system, not ten isolated files.

4.9.2 .claude/agents/—subagents (the model delegates)🔗

A subagent is a Markdown file with frontmatter (name, description, allowed tools, optionally a model) whose body is that agent’s system prompt. Subagents run with their own context window and can be tool-restricted.

TerpTasks’ shared reviewer (question 5 from the running example), .claude/agents/code-reviewer.md:

---
name: code-reviewer
description: Reviews diffs for bugs, missing tests, and convention
  violations. Use proactively after significant code changes.
tools: Read, Grep, Glob, Bash(git diff:*), Bash(pytest:*)
---
You are the TerpTasks code reviewer. For the diff you are given:
1. Check conformance with CLAUDE.md conventions (timezone-aware UTC,
   type hints, tests for behavior changes).
2. Look for bugs and missing edge cases, especially around datetime
   comparisons.
3. Report findings as a prioritized list: blocker / should-fix / nit.
Do not edit any files. You review; the main agent fixes.

Walk the design: the description tells the main agent when to delegate (“use proactively...”); the tools line is least privilegea reviewer needs to read and diff, never to edit; the body pins the rubric so Alice’s and Bob’s reviews come out consistent. This is a role description for a specialist teammate, checked into Git.

4.9.3 .claude/skills/—packaged know-how (the model matches)🔗

A skill is a directory containing a SKILL.md (frontmatter: name + description; body: instructions) plus any supporting files (scripts, templates, references). The crucial mechanic is progressive disclosure: at session start only names and descriptions are in context; the model loads a skill’s full body only when your request matches it. That’s why skills scale to dozens without burning your context window—and why the description field is the single line that determines whether a skill ever fires.

A TerpTasks example, .claude/skills/db-migration/SKILL.md:

---
name: db-migration
description: Write and verify TerpTasks database schema migrations.
  Use when the user asks to change the task schema or storage layer.
---
When writing a TerpTasks migration:
1. Migrations live in migrations/, numbered NNN_description.sql.
2. Every migration needs a paired rollback in the same file under
   a `-- rollback` marker.
3. After writing one, apply it to a scratch DB and run `pytest -q`.
4. Never modify an already-committed migration; add a new one.

4.9.4 Hooks—deterministic automation (the harness triggers)🔗

Hooks close the loop we opened last lecture (“preferences go in CLAUDE.md; requirements go in hooks”). Mechanically: hook configuration lives in settings.json (so it rides the same scope/precedence system), and the scripts it invokes conventionally live in .claude/hooks/. Hooks fire on lifecycle events—PreToolUse (can block the tool call), PostToolUse, UserPromptSubmit, Stopmatched against tool names.

TerpTasks enforces formatting the guaranteed way. In .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command",
            "command": ".claude/hooks/format.sh" }
        ]
      }
    ]
  }
}

And .claude/hooks/format.sh:

#!/bin/bash
# Reads hook JSON on stdin; formats the file that was just edited.
file=$(jq -r '.tool_input.file_path // empty')
if [[ "$file" == *.py ]]; then
  ruff format "$file" >/dev/null 2>&1
fi
exit 0

Trace: agent edits store.py → harness fires PostToolUse → matcher Edit|Write matches → script runs → ruff format store.py. Every time, whether or not the model remembered, because the harnessplain code, not a model—executes it. A PreToolUse hook exiting with code 2 blocks the call, which is how organizations enforce “no edits to migrations/” as physics rather than as a request.

Security note. Hooks are arbitrary code executing with your privileges on model-triggered events. Review them in cloned repos exactly as skeptically as you’d review a Makefile before running makeand remember they arrive via Git from whoever wrote the repo.

4.10 GitHub-Ready Mini-Project: the TerpTasks Configuration Kit🔗

Everything from the preceding sections assembled into a committable overlay for last lecture’s terptasks repository. (Only settings.local.json stays out of Git—shown here as an example for each developer to write their own.)

4.10.1 Structure🔗

terptasks/

|- CLAUDE.md                          # from last lecture, +one line for migrations

|- .mcp.json                          # GitHub MCP server

|- .claude/

|  |- settings.json                   # team permissions + hooks

|  |- settings.local.json             # EXAMPLE ONLY -- each dev writes their own

|  |- commands/

|  |  |- release-check.md             # from last lecture

|  |  +- fix-issue.md

|  |- agents/

|  |  +- code-reviewer.md

|  |- skills/

|  |  +- db-migration/

|  |     +- SKILL.md

|  +- hooks/

|     +- format.sh                    # chmod +x

+- src/, tests/, pyproject.toml       # unchanged from last lecture

4.10.2 The complete settings.json🔗

Merging the permissions and hooks shown earlier:

{
  "permissions": {
    "allow": [
      "Bash(pytest -q)",
      "Bash(pytest tests/*)",
      "Bash(ruff check:*)",
      "Bash(ruff format:*)",
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)"
    ],
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Bash(git push:*)"
    ]
  },
  "env": {
    "TERPTASKS_ENV": "development"
  },
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/format.sh" }
        ]
      }
    ]
  }
}

All other files appear verbatim in the sections above. Setup on a fresh clone:

git clone <your-fork-url> terptasks && cd terptasks
chmod +x .claude/hooks/format.sh          # ensure the hook is executable
claude    # trust prompt appears for .mcp.json -> review, accept

4.10.3 Verifying the kit (the “tests” of a config project)🔗

Configuration deserves verification like any code. From inside a session:

Check

How

Expected

permissions loaded

/permissions

allow/deny lists visible with their sources

deny works

ask Claude to read .env

blocked by rule, not by the model's politeness

allow works

ask Claude to run pytest -q

runs with no approval prompt

hook fires

ask for a deliberately mis-formatted edit to store.py

file is ruff-formatted on disk afterward

MCP up

/mcp

github server listed as connected

command works

/fix-issue 1

expands with 1 substituted

agent visible

/agents

code-reviewer listed from project scope

4.11 Practice Exercises🔗

4.11.1 Basic🔗
  1. E1. For each of the three questions in the motivation (know / may do / can use), name every file from the priority table that answers it.

  2. E2. Without looking, write the settings precedence chain from highest to lowest. Then check yourself against The Scope Model and Precedence and note anything you inverted.

  3. E3. Classify each item into CLAUDE.md, settings.json, settings.local.json, or ~/.claude/settings.json, with one-sentence justifications: (a) “we use Poetry, not pip”; (b) allow poetry run pytest; (c) “I personally prefer seeing diffs before edits are applied”; (d) deny reading secrets/; (e) allow git status in every repo you own.

  4. E4. Alice’s local file allows Bash(rm -rf build); the project file denies Bash(rm:*). What happens when the agent tries rm -rf build in Alice’s session, and why?

  5. E5. Explain to a teammate, in three sentences, why ~/.claude.json should never appear in a Git repository even though .claude/settings.json should.

  6. E6. Build the GitHub-Ready Mini-Project: the TerpTasks Configuration Kit kit onto your terptasks fork and run the entire verification table, capturing evidence (screenshots or transcript lines) for each row.

  7. E7. Write a PreToolUse hook that blocks any Edit or Write whose target path is under migrations/, exiting with code 2 and a clear message. Demonstrate the block and explain why this beats a CLAUDE.md sentence saying the same thing.

  8. E8. Create a personal (~/.claude/commands/) command /standup that summarizes your last day of Git activity across any repo. Then explain in writing why this one belongs at user scope while /release-check belongs at project scope.

  9. E9. Design a test-engineer subagent for TerpTasks: write the full frontmatter (with a least-privilege tools: line) and system prompt. Justify every tool you granted—and every one you withheld.

  10. E10. Take the db-migration skill and deliberately break its description (make it vague: “helps with databases”). Verify the skill stops firing for a schema-change request, then fix it. What does this teach about how skills are matched?

  11. E11. Config-review lab. Trade configuration kits with another student. Audit theirs for: over-broad allows, missing denies, hooks that could be exploited, and secrets that leaked into committed files. Write findings as blocker/should-fix/nit—using their own code-reviewer agent’s rubric against them is encouraged.

  12. E12. The malicious-repo thought experiment. You clone an untrusted repository containing .claude/settings.json, .mcp.json, hook scripts, and a CLAUDE.md. Enumerate, file by file, what each could attempt to do to you, at what moment (clone? first claude launch? first edit?), and which layer of Claude Code’s design (trust prompts, git-ignored local files, permission asks, enterprise policy) mitigates each. Conclude with the one file you’d inspect first and why.

  13. E13. Org policy design. You are the security lead for a 30-developer company adopting Claude Code. Design the enterprise managed-policy layer plus a recommended project template: what is denied everywhere and why; what is allowed everywhere; what is left to project scope; how hooks enforce your audit requirements. Deliver as a 1–2 page policy document plus the actual JSON.

4.12 Summary🔗

4.12.1 Key takeaways🔗
  1. Three questions organize everything: context files say what the agent knows (CLAUDE.md), policy files say what it may do (settings*.json, hooks), capability files say what it can use (.mcp.json, agents, skills, commands).

  2. Scope is the master concept: user → project → local, with more-specific winning and deny winning absolutely. It’s Git config layering, reborn.

  3. Prose advises; JSON enforces. “Don’t read .env” in CLAUDE.md is a request; "deny": ["Read(.env)"] is a guarantee.

  4. The four directories differ by trigger: commands (you), agents (delegation), skills (matching), hooks (the harness, deterministically).

  5. Committed config is team infrastructurereviewed in PRs, inherited on clone. settings.local.json is the experiment lane; good rules graduate.

  6. State ≠ config: ~/.claude.json belongs to the tool. Don’t edit it, never commit it.

  7. Config from strangers is code from strangers. Trust prompts exist for .mcp.json and hooks because a cloned repo configures your agent.

4.12.2 Terminology🔗

Term

Meaning

scope

where a setting lives: user, project, or local

precedence

conflict resolution: enterprise > CLI > local > project > user

permission rule

allow/ask/deny pattern like Bash(git diff:*)

memory file

CLAUDE.md at any scope; they stack rather than override

import

an @path line in CLAUDE.md pulling another file into context

MCP server

external tool provider declared in .mcp.json or user scope

state file

~/.claude.json: auth, trust, caches -- tool-owned

subagent

frontmatter + system prompt in agents/; own context, restricted tools

skill

directory with SKILL.md; loaded on demand via its description

progressive disclosure

only skill names/descriptions in context until one matches

hook

harness-executed script on lifecycle events; can block tool calls

least privilege

granting a component only the tools it needs

4.12.3 Common mistakes🔗
  • Putting enforcement wishes in CLAUDE.md and wondering why they’re occasionally ignored.

  • Committing settings.local.json (or worse, secrets in .mcp.json).

  • Bash(*)-style allows that eliminate the ask layer entirely.

  • Editing ~/.claude.json by hand instead of using claude mcp add / /login.

  • Vague skill descriptions, so the skill never fires (or fires constantly).

  • Granting subagents Edit when their job is to review.

  • Trusting a cloned repo’s .claude/ contents without reading them.

4.12.4 Connections🔗
  • Backward: this lecture is the durable half of How to Effectively Use Claude Code; CLAUDE.md and /release-check from there now sit inside a full kit. The scope chain mirrors the git lecture’s config layering; hooks-as-enforcement mirror CI merge gates.

  • Forward (MCP lecture): .mcp.json is the client-side view of the servers you’ll build.

  • Forward (security): E12’s threat model—configuration as an attack surface—returns when we discuss supply-chain risks alongside the OWASP material.