---
title: Customizing Claude Code
---

Claude Code is useful out of the box, but it gets genuinely good once you teach it your project and wire in your tools. Customization is mostly a few files Claude reads at startup, plus automations.

## CLAUDE.md — persistent project knowledge

`CLAUDE.md` (at the repo root or in `.claude/`) is a markdown file you commit so Claude knows your project: build and test commands, conventions, architecture, gotchas. It's read at the start of every session.

Run `/init` to have Claude draft one from your codebase, then trim it — a focused file Claude follows beats a long one it skims. Layered files load together, most-specific last:

- `~/.claude/CLAUDE.md` — your personal defaults, across all projects.
- `.claude/CLAUDE.md` — project instructions (committed).
- `.claude/CLAUDE.local.md` — personal, project-specific (gitignored).

Claude Code also keeps an **automatic memory** it writes itself — build commands it discovers, decisions, things you tell it — under `~/.claude/projects/\<project>/memory/`, indexed by a `MEMORY.md` that loads at session start. Run `/memory` to see and edit it.

## settings.json — config and permissions

`settings.json` lives in `~/.claude/` (you) or `.claude/settings.json` (project, shareable); `.claude/settings.local.json` stays on your machine. The part I touch most is permissions — pre-approve the safe things so Claude stops asking, and hard-block the dangerous ones:

```json
{
  "permissions": {
    "allow": ["Bash(npm run test:*)", "Bash(git status)", "Read(./src/**)"],
    "deny": ["Bash(rm -rf *)", "Read(./.env)"],
    "defaultMode": "acceptEdits"
  },
  "env": { "NODE_ENV": "development" }
}
```

`defaultMode` sets the permission mode each session starts in (`default`, `acceptEdits`, or `plan`; `bypassPermissions` exists for sandboxes). `env` vars propagate to the commands Claude runs.

## Hooks — automate on events

Hooks run a command at points in Claude's lifecycle — formatting after an edit, linting before a commit, blocking a risky command. They're configured by event, then matcher, then handlers:

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

Common events include `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, and `Stop` (there are more). On tool events you can add an `if` filter like `"if": "Bash(git \*)"` to scope a hook to matching calls. Your script receives JSON on stdin; exit non-zero to block the action.

<Callout type="warning">
  Hooks run real shell commands automatically, with your environment and permissions. Treat one like any script you'd run unattended — keep them in version control and review them before trusting them.
</Callout>

## Skills — reusable commands

When you keep pasting the same checklist or procedure, make it a **skill**: a `SKILL.md` with YAML frontmatter at `.claude/skills/\<name>/SKILL.md` (personal: `~/.claude/skills/...`). The folder name becomes the command — `/deploy`, say. Claude can also invoke a skill on its own when its `description` matches the work; add `disable-model-invocation: true` to keep it manual-only.

```markdown
---
name: deploy
description: Deploy the current branch to staging
disable-model-invocation: true
---
1. Run tests and type-check.
2. Build and push the image.
3. Smoke-test the deployment.
```

A skill's body only loads when it's used, so long reference material costs nothing until you need it. (Older `.claude/commands/\<name>.md` files still work the same way.)

## MCP — connect external tools

The Model Context Protocol lets Claude Code read and act on outside systems — GitHub, Jira, Slack, a database, your own service — instead of you pasting data into chat. Add a server with `claude mcp add`:

```bash
# Remote HTTP server
claude mcp add --transport http notion https://mcp.notion.com/mcp

# Remote SSE server
claude mcp add --transport sse asana https://mcp.asana.com/sse

# Local stdio server (everything after -- runs the server)
claude mcp add --env API_KEY=value --transport stdio airtable -- npx -y airtable-mcp-server
```

`--scope` controls where it's stored: `local` (default — just you, in `~/.claude.json`), `project` (shared via a committed `.mcp.json`), or `user` (all your projects). Then you just ask in plain language and Claude routes the call.

## Subagents — specialized workers

A subagent is a separate assistant with its own context window, system prompt, and tool access. Define one as a markdown file with frontmatter in `.claude/agents/` (or `~/.claude/agents/`), or run `/agents` to create it interactively:

```markdown
---
name: code-reviewer
description: Reviews changes for quality, bugs, and security
tools: Read, Grep, Glob, Bash
model: sonnet
---
You are a senior reviewer. Focus on correctness, security, and clear naming.
Report findings grouped by severity.
```

Claude delegates to it when the description fits, or you can say "use the code-reviewer subagent." Subagents keep noisy work out of your main thread, let you restrict which tools are available, and can run on a faster, cheaper model like `haiku`.

## See also

<PageRef space="notes" slug="claude-code-getting-started" />

<PageRef space="notes" slug="claude-code-workflow" />
