Claude Code: technical tutorial
Claude Code: ReviewInstall & set upNewsTechnical 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.SessionStartandSessionEnd: 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.jsonin 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 andCLAUDE.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; setANTHROPIC_API_KEY.--output-format json: the text result is in.result, plussession_idand a cost estimate (total_cost_usd). With--json-schemayou get structured output in.structured_output.--output-format stream-json --verbose: events as separate JSON lines.--permission-mode:dontAskrefuses everything that would otherwise produce a question, handy for locked-down CI.acceptEditsallows file edits.autolets a classifier monitor.--permission-prompts none(from 2.1.259): for unattended runs; everything that would produce a question is refused.--continueor--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_URLwithout 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.mdunder 200 lines. Put task-specific instructions in skills or in rules withpaths:frontmatter, so that they only load when needed. - Use
--barein scripts, so that no unnecessary context is loaded. - For scripted runs, look at
total_cost_usdin the JSON output. That is an estimate on the client side, not an invoice.
Common errors
claude: command not foundafter installation —~/.local/binis not inPATH; 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 doctorpoints out the file. - Claude ignores your
AGENTS.md— There is aCLAUDE.mdorCLAUDE.local.mdsomewhere above your working directory; in/config, set Project instructions toclaude-md-and-agents-md. - MCP server missing in a
-prun — Invalid entry in--mcp-config; checkmcp_server_errorsin thesystem/initevent ofstream-json. - Sandbox does nothing on Linux —
bubblewraporsocatis missing;/sandboxshows what is missing.
Documentation: code.claude.com/docs.