# Configuring Claude Code: From Defaults to Engineering Partner

> The settings that genuinely move the needle in Claude Code: a lean context, correct hooks, permission modes, effort control and subagents — with the real mechanisms instead of the half-truths making the rounds.

Source: https://www.jpkc.com/db/en/blog/claude-code-konfiguration/

Claude Code's default settings are good enough to be productive right away — and that's exactly the trap. "Good enough" leaves a surprising amount on the table: speed, but above all control. Claude Code isn't a black box you accept as it comes; it's a **configurable engineering platform**. The quality you end up with depends not only on the model but at least as much on the system you build around it.

This isn't a beginner's guide. It's for people who already use Claude Code daily and want more consistent, more reliable results. I'll walk through the knobs that actually pay off — in four blocks: **context**, **safety & automation**, **how you work with the agent**, and **more perspectives through models and subagents**. For each setting I name the *real* mechanism, because several of these attract configurations that simply don't work. Every example is yours to copy directly.

> **Updated in October 2026** for Claude Code 2.1.294: a lot has moved since July — auto mode is the start mode, new models come with a different default effort, and there are Ultracode and path-scoped rules. A fact check against the official docs also corrected a few claims from the first version, including those on `/effort`, thinking keywords and skill front matter.

## Block 1 — Feed context deliberately

Everything starts with what Claude sees in every session before you type a single word. Be generous here and you think you're doing the model a favor. The opposite is true.

### Keep CLAUDE.md small

`CLAUDE.md` is your permanent session context: its contents land in the prompt of **every** conversation before anything else happens. The temptation is to grow this file into an encyclopedia — architecture, conventions, domain knowledge, onboarding, deployment steps, API contracts, project history. "More context, better answers," so the assumption goes.

It doesn't hold. Large language models don't weigh every part of a long document equally; information buried in the middle of long texts reliably gets lost — the well-known **"lost in the middle"** effect. The bigger `CLAUDE.md` grows, the more likely Claude is to overlook the very rule you cared about. So you pay twice: more tokens per session *and* less reliable attention.

The rule of thumb, therefore: `CLAUDE.md` should hold only what belongs in *every single* session.

- technology stack
- cross-cutting coding principles
- hard architectural guardrails
- project-wide rules
- links to further documentation

Everything else moves out. Anthropic now names a target itself: **under 200 lines per `CLAUDE.md`**. Claude Code warns at startup and in `/status` when a file is over that — and also when several mid-sized files and `@` imports together exceed a combined limit. `/doctor` proposes cuts for content Claude can derive from the code anyway, and `/doctor prompt-audit` finds outdated or contradictory instructions.

### What else lands in every session

`CLAUDE.md` isn't the only source of permanent context:

- **`AGENTS.md`:** Since Claude Code 2.1.277, an `AGENTS.md` takes over as the project instructions when there is no `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` in the working directory or above it. If one of those exists, Claude reads only the CLAUDE.md files — unless you set "Project instructions" in `/config` to `claude-md-and-agents-md` or import the file with `@AGENTS.md`. The same rule applies to `AGENTS.md`: keep it lean.
- **Auto memory:** Claude writes its own notes to `~/.claude/projects/<project>/memory/`. The first 200 lines or 25 KB of `MEMORY.md` land in every local session alongside `CLAUDE.md`; topic files in the same folder are read only when needed. "Remember X" goes there, not into `CLAUDE.md` — if you want it in `CLAUDE.md`, say so explicitly. `/memory` lets you see and prune the notes (plain Markdown) and switch the feature off; alternatively set `"autoMemoryEnabled": false` in the project settings or `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`.

### Move detail out modularly — with the real mechanisms

The question then is: where does the rest go? Claude Code offers three routes, and it's worth not confusing them.

**`@` imports in `CLAUDE.md`.** Any `@path/to/file.md` in `CLAUDE.md` pulls that file in at session start — even mid-sentence. Only paths inside backticks or code blocks stay plain text; relative paths resolve from the importing file. This keeps the main `CLAUDE.md` lean as a table of contents while the detail lives in focused files:

```markdown
# Project conventions

Stack: NestJS, PostgreSQL, TypeScript (strict).
Architecture: clean architecture, the repository pattern is mandatory.

Details when needed:
@docs/architecture.md
@docs/testing-strategy.md
@docs/api-contracts.md
```

This is still permanent context — the imported files are loaded along with it. `@` imports organize; they don't reduce. For genuine on-demand loading you need one of the other two routes.

**Path-scoped rules — loaded when Claude touches matching files.** Rules under `.claude/rules/*.md` with `paths:` front matter load only when Claude reads, writes or edits a matching file:

```markdown
---
paths:
  - "src/modules/**/*.ts"
---

- Business validation never uses class-validator.
- The repository pattern is mandatory.
```

Rules *without* `paths:` load at every start like `CLAUDE.md` — only the path-scoped ones save context. A `CLAUDE.md` in a subdirectory behaves the same way: it joins only when Claude works there. Since 2.1.293 this also kicks in when Claude views a file with `cat`, `head`, `tail`, `sed -n` or `grep` in the shell instead of the Read tool.

**Skills — loaded when the task fits.** A skill is not some arbitrary Markdown lying around that gets read automatically. A skill is a **directory** under `.claude/skills/` with a `SKILL.md`. Its YAML front matter should carry a `description`, because Claude decides from it when the skill fits; `name` is optional and defaults to the directory name:

```
.claude/skills/
├── nestjs-conventions/
│   └── SKILL.md
├── migration-playbook/
│   └── SKILL.md
└── code-review-checklist/
    └── SKILL.md
```

```markdown
---
name: nestjs-conventions
description: Conventions for NestJS modules — when to load and what for
---

## Module structure

domain/ · application/ · infrastructure/ · presentation/

## Rules

- Business validation never uses class-validator.
- The repository pattern is mandatory.
- Lifecycle hooks belong only in the application layer.
```

The decisive difference: Claude reads the `description` and **loads the skill only when it fits the task at hand** — the NestJS conventions for a NestJS module, the review checklist for a pull-request review. The rest stays out. This is lazy loading for project knowledge: less noise, fewer tokens, and a far smaller chance that rules from unrelated parts of the project get mixed up.

The rule of thumb: conventions tied to particular files or directories belong in path-scoped rules or a nested `CLAUDE.md`. Procedures, playbooks and checklists tied to a *task* rather than a file belong in skills.

> **Key point:** `@` imports *structure* permanent context; path-scoped rules and skills *load it on demand*. A small `CLAUDE.md` plus curated rules and skills beats any monster file — regardless of how clever your prompts are.

The same pattern works globally, by the way: a lean `~/.claude/CLAUDE.md` for your personal, cross-project ground rules, with project-specific knowledge staying in each repo.

## Block 2 — Safety and safe automation

The second block is where most setups are either too timid (constant approval prompts) or too reckless (everything allowed). Both can be tuned precisely — once you know how the mechanisms actually work.

### `settings.json`: get permissions right

First a change many people missed: since v2.1.284, new interactive sessions in the terminal and VS Code start in **auto mode** on every plan and provider (more below) and hardly ask anymore. If you want the agent to ask before changes, start in **Manual** mode: `claude --permission-mode manual` or `"permissions": { "defaultMode": "default" }` in your user or project settings (VS Code reads only user settings or `claudeCode.initialPermissionMode`).

In Manual mode, reading files, searching and read-only Git commands such as `git status`, `git diff` or `git log` already run without a prompt. It asks about everything else — including harmless verification commands like tests, lint or typecheck. The fix is an `allow` list for exactly those, while everything that changes something stays behind manual approval:

```json
{
  "permissions": {
    "allow": [
      "Bash(npm test:*)",
      "Bash(npm run lint:*)",
      "Bash(npm run typecheck:*)"
    ],
    "deny": [
      "Bash(git push:*)",
      "Bash(git reset --hard:*)",
      "Bash(rm -rf:*)"
    ]
  }
}
```

The effect is immediate: instead of twenty harmless approvals per session you only sign off on what can actually change something. It feels far more like pair programming than remote control.

It's important to understand **how these rules match** — this is where many well-meaning configurations fall apart. The pattern inside `Bash(...)` is matched against the **command text** Claude writes, **per subcommand**: Claude Code first splits compound commands (`&&`, `||`, `;`, `|`, …) and checks each part on its own; it strips wrappers such as `timeout`, `nice` or `nohup`. `*` stands for any text, so `Bash(npm test *)` (or the equivalent older form `Bash(npm test:*)`) matches every `npm test …`.

A `deny` rule only catches the usual spelling: `/bin/rm -rf`, `bash -c 'rm -rf …'`, `rm -fr` or `git -C . push` get past `Bash(rm -rf:*)` and `Bash(git push:*)` — the docs state explicitly that such rules are not a security boundary. What also follows: rules like `Bash(drop table:*)` or `Bash(delete from ...)` are useless. SQL virtually never runs as a bare shell command; it's embedded — e.g. `psql -c "drop table users"`. The real command string then starts with `psql`, not `drop table`, and the rule matches nothing. If you want to catch dangerous SQL, you need a hook that inspects the command's contents with your own logic — which brings us to the next point. A hook still only compares text, though; real enforcement independent of the command text comes from the sandbox (see below).

### Hooks — the correct way

Hooks are small shell programs Claude Code runs before or after a tool call. They're one of the most powerful and, at the same time, most frequently miswired features. The essentials you have to know:

1. **A hook receives its data as JSON on `stdin`** — not as a positional argument. The command to be run sits in `.tool_input.command`.
2. **A `PreToolUse` hook blocks with exit code `2`.** Whatever the hook writes to `stderr` reaches Claude as the reason. Exit `0` means "no objection" (the normal approval flow continues); any other code counts as a hook error and does *not* block.

So a hook that reads the command from `$1` and tries to block with `exit 1` simply does nothing. Here's a guard that actually bites:

```bash
#!/usr/bin/env bash
# PreToolUse hook: refuse obviously destructive shell commands.
# Claude Code passes the tool call as JSON on stdin.
input=$(cat)
command=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')

dangerous='rm -rf /|mkfs|dd if=.*of=/dev/|DROP TABLE|TRUNCATE|DELETE FROM.*WHERE 1=1'
if printf '%s' "$command" | grep -qiE "$dangerous"; then
  echo "Blocked: refusing to run a destructive command." >&2
  exit 2   # exit code 2 tells Claude Code to block the call
fi
exit 0
```

You register it in `settings.json`. Here the `matcher` filters on the **tool name** (`Bash`), not on a command:

```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard.sh"
      }]
    }]
  }
}
```

Instead of using exit codes, a hook can also finish with `0` and return a JSON object that makes the decision explicit (`permissionDecision: "deny"` with a reason) — finer-grained, but for the simple blocking case exit `2` is entirely enough. Besides such `command` hooks there are hooks of type `prompt` and `agent`, where a model decides. Since v2.1.294 they also reliably judge rules written as instructions ("Block commands that …"); before, such hooks could let through exactly what they were meant to block.

Two more things for context: the `matcher` filters on the tool name for the tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied`). For `Notification` it matches on the notification type (such as `permission_prompt` or `idle_prompt`), and `Stop` has no matcher at all — the hook always fires, and a matcher there is silently ignored. And a `Stop` hook (fires as soon as Claude finishes responding) or a `Notification` hook on `permission_prompt` (Claude is waiting for approval) that pings you via `notify-send` or `osascript` — or right in the terminal via the hook's `terminalSequence` output — saves you from constantly staring at the terminal. Trivial, but in practice the removal of that context switch is worth a surprising amount. Careful with `idle_prompt`: it only fires about 60 seconds after Claude finishes.

### `acceptEdits` deliberately — and understanding the permission modes

`acceptEdits` removes the confirmation before every file change and makes the agent feel noticeably faster. The first time, you wonder why it isn't always on. The answer: because "fast" is deceptive here.

The mode is exactly right for tasks that are **mechanical and automatically verifiable** — migrating JavaScript to TypeScript, replacing `any` types, renaming files, updating imports, formatting, generating tests for existing functionality, editing docs. Here the test suite reliably catches a mistake.

For anything that requires **judgment**, manual approval stays on: new features, architectural refactoring, authentication, payment flows, the database layer — in short, any task whose "done" can't be checked by a machine in seconds. The reason isn't distrust of the model but a simple experience: an agent already editing the files will happily "help" by refactoring services on the side that nobody wanted touched. Technically not broken, often even plausible — but no better than the architecture you deliberately chose. What should have been five minutes of cleanup turns into thirty minutes of review.

The rule behind it: **enable `acceptEdits` only where tests reliably detect a mistake.** If no test can tell you something went wrong, the extra click of manual approval is well spent.

`acceptEdits` is only one of several permission modes. It pays to know the whole ladder:

| Mode | Behavior |
|---|---|
| `default` (shown as Manual in the CLI and UI; `manual` is also accepted) | Asks before changing actions; reading runs through |
| `acceptEdits` | Auto-approves file edits and common filesystem commands |
| `plan` | Claude explores and proposes a plan without editing your source files; with auto mode available, classifier-approved shell commands also run |
| `auto` | Start mode for interactive sessions when nothing else is configured: no routine prompts. Rules apply first; reads and in-project edits run through. A classifier reviews the rest and blocks anything beyond your request |
| `dontAsk` | Auto-denies prompts; only pre-approved tools run |
| `bypassPermissions` | Skips all checks — only defensible inside a container/VM |

Two additions for everyday use: the built-in **bash sandbox** (`/sandbox`) fences shell commands via OS primitives and reduces prompts without switching off safety checks. And if you want fewer prompts without blindly waving everything through, the **`auto` mode** with its classifier is the more honest route than `bypassPermissions`. Since v2.1.284 it's the start mode anyway; `permissions.defaultMode` overrides that. On entering auto mode, broad `allow` rules such as `Bash(*)` are dropped so they can't sidestep the classifier. The latter (`--dangerously-skip-permissions`) belongs strictly inside a real isolation boundary — more on that shortly.

### Treat environments differently — the real mechanism

Your local development machine isn't production. It's natural to grant different environments different rights — generous locally, strict near production. Except this does **not** work via an environment variable pointing at a profile file (there is no such thing), and the command is simply `claude`, not `claude code`. The real mechanism is the **settings hierarchy**, and it's more capable:

- `~/.claude/settings.json` — your user defaults across all projects
- `.claude/settings.json` — project settings, **committed**, for the whole team
- `.claude/settings.local.json` — local override, per machine. It stays out of Git automatically only if Claude Code creates it (it then adds the file to your global Git excludes). If you create it by hand, add it to `.gitignore` yourself.

These layers are **merged automatically**: for single values the more specific file wins (local over project over user). Permission lists from all files, however, are *combined* and always evaluated in the order deny → ask → allow. A local `allow` rule adds freedoms but can't lift a committed `ask` or `deny`. The shared, safe baseline thus lives in the committed `settings.json`, and each machine adds its local freedoms in `settings.local.json` (running `docker compose down` or `npm run db:reset` without a prompt, say) — with no alias acrobatics. For a single run, `--settings` layers one more file on top. It doesn't replace the hierarchy but merges with it, permission lists included; to load only certain levels — say, the project file without your user and local settings — combine it with `--setting-sources`:

```bash
claude --setting-sources project --settings .claude/settings.ci.json
```

For teams and organizations, **managed settings** sit right at the top: server-managed settings from the admin console, MDM or OS-level policies, or `/etc/claude-code/managed-settings.json` (plus `managed-settings.d/`) on Linux. By default Claude Code uses only the highest-ranked source that delivers a policy, and its keys override user, project, local and `--settings` values — ideal for anchoring central guardrails nobody can override locally. And if you want to move the entire configuration directory, set `CLAUDE_CONFIG_DIR` to the new path (that controls the *directory*, not a single file).

> **In practice:** permission rules govern *what may run and whether it asks first*. But they are not a wall: a command that gets through can reach whatever the process can reach. So if you want to give the agent real autonomy — keyword `bypassPermissions` — pair the permissions with **isolation** (container, VM, sandbox runtime). How to do that cleanly is covered in the in-depth piece [Claude Code in a Container](https://www.jpkc.com/db/en/blog/claude-code-container/).

## Block 3 — How you work with the agent

Configuration is one half. The other is how you deal with the agent while it runs — this is where you save time without any setting at all.

### Recognize and abort context rot

Every long session eventually reaches a point where quality tips over. The symptoms are remarkably consistent: the agent proposes ideas you rejected long ago; it wants to change code it wrote itself five minutes earlier; it asks for context that has been chewed over several times. That's **context rot** — the context has become so full and contradictory that fresh signals get lost.

Pushing on doesn't help; it only gets more expensive. The fastest fix is almost always a clean restart. Claude Code gives you the tools for it:

- `/context` shows how full the context window actually is — often the trigger to notice.
- `/compact` summarizes the conversation and frees up room without losing the thread entirely.
- `/clear` starts completely fresh.

Instead of laboriously keeping a rotting session alive, write a short handoff when you restart. It costs less than five minutes and is more reliable than copying the whole transcript:

```markdown
## Context

### Goal
One sentence on the objective.

### Done
- File A: complete
- File B: complete

### Already rejected
- Approach X
- Approach Y

### Next step
One concrete task.
```

My personal threshold: if three consecutive messages fail to move the project forward, I restart without hesitation.

### The two-corrections rule

Closely related and at least as valuable: if you've corrected Claude **twice on the same issue** in one conversation, stop. Not because the model is stubborn, but because the conversation has become inefficient — each further clarification only brings marginal improvement, and you're going in circles.

In almost all cases one of three causes is to blame, and none of them is solved by the third correction:

1. **The task is too large.** Break it up. Claude solves three small, clearly bounded problems more reliably than one nested big one.
2. **The expected result is unclear.** Don't describe it — *show* it. A concrete example beats any abstract instruction.
3. **The starting point is too thin.** Instead of "build a service," sketch the interface, define the boundaries, or lay out a rough scaffold. Claude is dramatically more reliable at filling in an existing structure than inventing one from scratch.

The smartest prompt is sometimes not another correction but a fresh start with better starting material.

### Match effort to the task

Not every task deserves the same depth of thought. A typo needs no five-minute planning phase; a database migration very much does. Several levers work together here:

- **Effort** controls reasoning depth across the levels `low`, `medium`, `high`, `xhigh`, `max`. `/effort` with no argument opens a slider, and `/effort <level>` sets the level directly. Claude Code saves `low` to `xhigh` as the default for the current model; press `s` in the slider to apply a level to this session only, and `max` always applies to the current session only. `/effort auto` clears the saved level, and `--effort <level>` at startup sets it for one session. Important: Opus 5.5, Sonnet 5.5 and Haiku 5.5 start at `medium` — raise the level yourself for demanding work. Which levels you get depends on the model: Opus 4.6 and Sonnet 4.6 have no `xhigh`, and Haiku 4.5 has no effort control at all.
- **Ultracode** isn't an effort level but a separate toggle: `/effort ultracode` or `/effort ultracode off`, Tab in the `/effort` slider, or `"ultracode": true` in settings. With it on, Claude orchestrates dynamic multi-agent workflows for substantive tasks at whatever effort level is set. `claude --effort ultracode` starts with Ultracode on and `xhigh`; the word `ultracode` in a prompt switches it on for that one task only. It requires a model that supports `xhigh`.
- **Plan mode** (cycled with Shift+Tab or via `--permission-mode plan`) forces Claude to analyze first and present a plan before editing files.
- The keyword **`ultrathink`** in a prompt asks for deeper reasoning on that one turn without changing the session's effort level. Phrases like "think hard" are *not* keywords; Claude Code passes them through as ordinary text.
- And the **model choice** (`/model`) is the coarsest, often most effective lever of all.

A rough mapping:

| Effort | For what |
|---|---|
| **Low** — implement immediately, no planning | Utility scripts, tiny bugfixes, throwaway tooling |
| **Medium** — brief plan, then code | Everyday features, routine refactoring |
| **High** — detailed planning up front | New components, public APIs, larger architectural changes |
| **Maximum** — plan mode, approval, then implementation | Auth, core infrastructure, schema migrations, security, production-critical work |

A nice side effect: just deciding whether a task deserves the maximum level clarifies its risk. If `max` feels excessive, the task is smaller than you thought — if it feels right, you're wise to slow down.

## Block 4 — More perspectives: models and subagents

The final block flips the perspective. Instead of asking one model for the "best" solution, you let several tackle the same problem from different angles — especially valuable for decisions that are expensive to reverse: application architecture, module boundaries, database design, service contracts, event-driven systems, large refactors.

### Models as a review panel

The trick is a shift in mindset, not a feature. You ask the same question deliberately with a different focus — and, where it makes sense, with different models from the current lineup (as of October 2026 on the Anthropic API: Opus 5.5, Sonnet 5.5, Haiku 5.5 and, where available to you, Fable 5.1; switched via `/model opus`, `sonnet`, `haiku` or `fable` — on other providers the aliases may point to older models):

```text
Design this system with reliability and explicit contracts as the top priority.

Design the same system with development velocity and maintainability as the priority.

Solve the same problem with as little complexity and abstraction as possible.
```

The results rarely coincide. You usually get a conservative architecture, one optimized for speed, and a surprisingly elegant, minimalist variant. You're not looking for consensus but for **trade-offs**. Finally, in another session, you have the strongest ideas from all proposals synthesized under your project's constraints. If you work alone or don't always have a second experienced head available for design reviews, this comes surprisingly close to a real architectural sounding board.

### Subagents — the clean route there

The manual model panel is useful but clumsy. Claude Code has a dedicated feature for exactly these patterns that many people overlook: **subagents**. A subagent is a Markdown file under `.claude/agents/` (project) or `~/.claude/agents/` (all your projects) that takes on a bounded task with **isolated context** — optionally with its **own model** (`model:`), its own **effort level** (`effort:`) and its own **tool set** (`tools:`). Without `tools:` it inherits every tool; for a pure reviewer, restricting it to reading pays off. And because `opus` now points to Opus 5.5 with a default effort of `medium`, it's best to set the level right away for a serious review panel. Since v2.1.292 Claude can also pass a subagent an effort level per call — asking for it in the prompt is enough:

```markdown
---
name: architecture-critic
description: Independent reviewer for architecture proposals
model: opus
effort: high
tools: Read, Grep, Glob
---

You are a skeptical senior architect. Assess the proposed design
uncompromisingly along trade-offs: coupling, testability, operational
cost, reversibility. Name the weakest assumption first.
```

The gain is twofold. First, a subagent runs in **its own context** — it doesn't burden your main session with its mass of intermediate steps, actively working against context rot. Second, you can start **several in parallel**, each with a different focus or model, and get genuinely independent perspectives instead of one opinion colored by the same context. The review panel from above thus turns from manual copy-paste into a repeatable, cleanly separated workflow — and is at the same time the better way to handle limited context.

## Conclusion

None of these settings is complicated. Together, though, they transform Claude Code from a helpful autocomplete into something close to a reliable teammate: a lean `CLAUDE.md` with path-scoped rules and skills instead of a monster file, clear safety boundaries via correctly wired hooks and the real settings hierarchy, a deliberate way of working against context rot, and more perspectives through models and subagents.

The bigger lesson underneath: the quality of your AI assistant depends not on the model alone but at least as much on the system you build around it. Invest in that system, and the gains compound with every session. If you want to take the safety part seriously and give the agent real autonomy, you'll find the matching isolation layer in [Claude Code in a Container](https://www.jpkc.com/db/en/blog/claude-code-container/).

