# Claude Code technical tutorial: configuration and automation | llmnet Apps

[← Apps overview](/en/)

# Claude Code: technical tutorial

By Ivo Donker · October 28, 2026 — created with AI assistance (writer: DeepSeek V4.1 Flash; critic: Gemini 3.8 Flash)

Claude Code: [Review](/en/claude-code-review)[Install & set up](/en/claude-code-installatie)[News](/en/claude-code-nieuws)[Technical tutorial](/en/claude-code-tutorial)

As of September 2026, compiled from our own notes and news archive; text machine-generated and editorially reviewed. Check versions and commands in the official documentation.

This tutorial is for developers who want to configure Claude Code beyond the default: settings files and their order, permission rules, hooks, MCP servers, the sandbox, headless use in scripts and CI, and a custom gateway. All keys and commands have been checked against the official documentation (as of September 2026, Claude Code 2.1.28x).

## Settings files and their order

Claude Code reads settings from five layers. A higher layer wins over a lower one:

- Managed settings (managed-settings.json, MDM or the admin console): by your organisation, not to be overridden.

- Command line: claude --settings '<json-or-path>', only for that session.

- Project local: .claude/settings.local.json, only for you, does not go into git.

- Project shared: .claude/settings.json, goes into the repository.

- User: ~/.claude/settings.json, for all your projects.

The files are strict JSON: a // comment or one comma too many gives an error message on the next start. Put the schema line at the top for autocomplete in your editor:

{
 "$schema": "https://json.schemastore.org/claude-code-settings.json",
 "model": "claude-opus-5-5"
}
After a change, use /status to check which sources are loaded. You can try a single value temporarily without saving anything with --settings, with a dedicated flag (--model, --effort) or an environment variable (ANTHROPIC_MODEL).

## Permissions

Each rule names a tool and what it may do. This example allows lint and test commands without asking and blocks reading .env files:

{
 "permissions": {
 "allow": [
 "Bash(npm run lint)",
 "Bash(npm run test *)"
 ],
 "deny": [
 "Read(./.env)",
 "Read(./.env.*)"
 ]
 }
}
Note the space in Bash(npm run test *): the * makes it a prefix match. Without a space, Bash(git diff*) also matches git diff-index.

The default mode (permissions.defaultMode) determines how much is asked. The auto and bypassPermissions modes only work from your user or managed settings, not from a project file: a cloned repository therefore cannot give itself a free pass.

## Hooks

Hooks are shell commands that run at fixed moments, regardless of what the model decides. With them you enforce things that are only a request in CLAUDE.md. The structure is: event → list of matchers → per matcher a list of hooks.

{
 "hooks": {
 "PostToolUse": [
 {
 "matcher": "Write|Edit",
 "hooks": [
 {
 "type": "command",
 "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/lint.sh"
 }
 ]
 }
 ]
 }
}
Commonly used events:

- PreToolUse: before a tool call; suitable for blocking something.

- PostToolUse: after a successful tool call, such as linting or formatting after an edit.

- UserPromptSubmit: when you send a message.

- SessionStart and SessionEnd: start and end of a session.

- Stop: when Claude has finished responding.

- PreCompact: just before the context is summarised.

Hooks can be in any settings file, but also in a plugin (hooks/hooks.json) or in the frontmatter of a skill or subagent.

## Connecting MCP servers

An external (HTTP) server:

claude mcp add --transport http github https://api.githubcopilot.com/mcp/
A local server that runs via stdio; the -- separates Claude Code's options from the server's command:

claude mcp add --transport stdio --env API_KEY=xyz mijn-server -- npx -y mijn-mcp-server
With --scope you determine where the configuration goes:

- local (default): only this project, only for you, stored in ~/.claude.json.

- project: in .mcp.json in the project root, intended to be committed.

- user: all your projects, only for you.

A .mcp.json looks like this:

{
 "mcpServers": {
 "docs": { "type": "http", "url": "https://example.com/mcp" },
 "lokaal": { "type": "stdio", "command": "npx", "args": ["-y", "server-package"] }
 }
}
In a session, /mcp shows the status of each server, and there you also complete an OAuth login.

Security. A .mcp.json in a repository you have just cloned is someone else's code. Interactively, Claude Code asks for permission per server. With claude -p this does not happen (see below).

## The sandbox

The sandbox lets Claude run most shell commands without asking every time. In return, you determine which files and domains those commands may touch. The operating system enforces this: Seatbelt on macOS, bubblewrap on Linux and WSL 2. Native Windows is not supported.

On Linux, you first install the dependencies:

sudo apt-get install bubblewrap socat
The /sandbox panel shows the status and lets you choose a mode. In settings.json:

{
 "sandbox": {
 "enabled": true,
 "filesystem": {
 "allowWrite": ["/tmp/build"],
 "denyRead": ["~/.ssh"]
 },
 "network": {
 "allowedDomains": ["github.com", "*.npmjs.org"]
 }
 }
}
By default, Claude Code simply continues without a sandbox if it cannot start, with only a warning. If the sandbox must be a hard requirement, set sandbox.failIfUnavailable to true. If you do not want Claude to retry a blocked command outside the sandbox, use "allowUnsandboxedCommands": false.

## Headless: scripts and CI

With -p (or --print), Claude Code runs without interaction. The exit code is 0 on success, so your script can respond to it.

claude -p "Draai de tests en repareer wat faalt" \
 --allowedTools "Bash,Read,Edit" \
 --permission-mode acceptEdits
Important options:

- --bare: skips automatically loading hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md. Recommended for CI, because the result then does not depend on what is on the machine. In bare mode Claude Code does not use your subscription login; set ANTHROPIC_API_KEY.

- --output-format json: the text result is in .result, plus session_id and a cost estimate (total_cost_usd). With --json-schema you get structured output in .structured_output.

- --output-format stream-json --verbose: events as separate JSON lines.

- --permission-mode: dontAsk refuses everything that would otherwise produce a question, handy for locked-down CI. acceptEdits allows file edits. auto lets a classifier monitor.

- --permission-prompts none (from 2.1.259): for unattended runs; everything that would produce a question is refused.

- --continue or --resume <session_id>: continue an earlier run.

An example with jq:

claude --bare -p "Vat README.md samen" --allowedTools "Read" \
 --output-format json | jq -r '.result'
Note: without --bare, a -p run loads the hooks from .claude/settings.json and the servers from .mcp.json of the directory in which it runs, without a trust prompt. So never run claude -p without --bare in an unknown repository.

## Via your own LLM gateway

If you run a gateway for key management, cost monitoring or logging, you point Claude Code to it with ANTHROPIC_BASE_URL and pass a gateway key. The gateway must offer a supported API format, with the Anthropic format first and foremost. Two pitfalls:

- If you set only ANTHROPIC_BASE_URL without a gateway key, your claude.ai login remains the active credential, with the limits and billing of your subscription.

- Anthropic does not support sending Claude Code through a gateway to non-Claude models. That can sometimes work technically, but features can then silently break.

Put the variables permanently in the env block of ~/.claude/settings.json, so that you do not have to export them per shell.

## Limiting costs

- Choose a model per task with /model, and the thinking effort with /effort. Not every task calls for the heaviest model.

- Keep CLAUDE.md under 200 lines. Put task-specific instructions in skills or in rules with paths: frontmatter, so that they only load when needed.

- Use --bare in scripts, so that no unnecessary context is loaded.

- For scripted runs, look at total_cost_usd in the JSON output. That is an estimate on the client side, not an invoice.

## Common errors

- claude: command not found after installation — ~/.local/bin is not in PATH; open a new terminal or add the path.

- Settings Error on startup — Invalid JSON (a comment or an extra comma) in a settings file; claude doctor points out the file.

- Claude ignores your AGENTS.md — There is a CLAUDE.md or CLAUDE.local.md somewhere above your working directory; in /config, set Project instructions to claude-md-and-agents-md.

- MCP server missing in a -p run — Invalid entry in --mcp-config; check mcp_server_errors in the system/init event of stream-json.

- Sandbox does nothing on Linux — bubblewrap or socat is missing; /sandbox shows what is missing.

Documentation: [code.claude.com/docs](https://code.claude.com/docs/en/overview).
