Skip to content
NLEN
← Apps overview

Claude Code: technical tutorial

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

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:

  1. Managed settings (managed-settings.json, MDM or the admin console): by your organisation, not to be overridden.
  2. Command line: claude --settings '<json-or-path>', only for that session.
  3. Project local: .claude/settings.local.json, only for you, does not go into git.
  4. Project shared: .claude/settings.json, goes into the repository.
  5. 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:

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:

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:

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:

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

Common errors

Documentation: code.claude.com/docs.