Ga naar hoofdinhoud

Global Claude Code settings (~/.claude)

These are mandatory settings for anyone working on Conduction projects with Claude Code. They enforce a read-first, write-with-approval policy at the user level, ensuring Claude cannot perform destructive operations without explicit confirmation. They also version-check themselves at the start of each session so you always know when an update is available.

Project files under .claude/ in this repo (for example settings.json with MCP allowlists) complement this; they do not replace the global policy.

Versioned canonical files

The canonical files live under global-settings/. The version is tracked in global-settings/VERSION.

FileInstall as
global-settings/settings.json~/.claude/settings.json
global-settings/block-write-commands.sh~/.claude/hooks/block-write-commands.sh
global-settings/block-config-tool-writes.sh~/.claude/hooks/block-config-tool-writes.sh
global-settings/check-settings-version.sh~/.claude/hooks/check-settings-version.sh
global-settings/sound-notify.sh~/.claude/hooks/sound-notify.sh (optional sound wrapper — silent by default; see Optional: notification sounds)

Install / update

See global-settings/README.md for install commands, update instructions, and the VERSION bump policy.

Session-start status panel

At the start of every Claude session, a live status panel is printed to the terminal (stderr):

┌──────────────────────────────────────────────┐
│ Global Claude Settings Status │
└──────────────────────────────────────────────┘
Installed : v2.0.0 ✓
Local repo : main @ v2.0.0
Online : v2.0.0 (via Codeberg)

Color coding:

  • Green — version matches / up to date
  • Yellow — local branch is ahead of installed (informational only)
  • Red — installed is behind online main (update required)

The "Online" line shows the fetch method used:

  • (via Codeberg) — fetched directly from Codeberg using curl against the raw URL (primary method, uses settings-repo-url)
  • (via git fetch) — fetched from origin/main of the local repo clone (fallback method, uses settings-repo-path)

If no local repo is configured, "Local repo" shows "(not configured)" instead of branch info.

If configuration issues are detected (missing config files, unreachable remote, curl not installed), they are shown in red below the panel — never silently skipped.

Note: The terminal panel is only visible when using Claude Code in the terminal (CLI). In the VS Code extension, hook stderr is not shown as a visible banner — see the Claude chat message below instead.

Session-start message in Claude chat

In addition to the terminal panel, the hook always injects a message into Claude's context at the start of every session. Claude will relay this at the top of its first response:

Settings up to date:

New session started — Global Claude Settings checked. Settings are up to date (v1.0.0).

Update required (prominently displayed, cannot be missed):

NEW SESSION — GLOBAL CLAUDE SETTINGS: UPDATE REQUIRED Installed: v0.1.0 (outdated) | Latest: v1.0.0 (on origin/main) Say "update my global settings to 1.0.0" to apply the update.

Configuration error (prominently displayed):

NEW SESSION — GLOBAL CLAUDE SETTINGS: CONFIGURATION ERROR [description of the issue]

Online version checking

The version check supports two methods for fetching the online version, tried in order:

If ~/.claude/settings-repo-url contains a Codeberg repo slug (e.g. Conduction/.github), the hook fetches VERSION via curl from https://codeberg.org/<slug>/raw/branch/<ref>/global-settings/VERSION (default ref: main). This method:

  • Does not require a local clone of the repo
  • Uses unauthenticated curl against the public raw URL (no token needed for public repos)
  • Is faster than git fetch (single HTTP request)
  • Falls back gracefully if curl is not installed or the HTTP fetch fails

The Codeberg /raw/branch/<ref>/ path resolves to the tip of <ref> on a branch — tag and SHA tracking are not supported via this method; configure settings-repo-path and use the git-fetch fallback for those cases.

2. Git fetch (fallback)

If the Codeberg method is not configured or fails, and ~/.claude/settings-repo-path points to a valid local clone, the hook falls back to git fetch origin <ref> --depth=1 followed by git show origin/<ref>:.... This is the original method.

Tracking a non-default branch

By default, both methods track the main branch. To track a different branch, write it to ~/.claude/settings-repo-ref:

echo "feature/claude-code-tooling" > ~/.claude/settings-repo-ref

When absent, the ref defaults to main.

Configuration options

Config fileRequired?Purpose
~/.claude/settings-repo-urlOptional (recommended)Codeberg repo slug for online raw-URL check
~/.claude/settings-repo-pathOptional (fallback)Path to the root of the canonical repo for git-based check
~/.claude/settings-repo-refOptionalBranch to track (defaults to main)

You can configure:

  • Both URL and path (recommended): Codeberg is tried first, local git as fallback
  • Only settings-repo-url: Works without any local clone; no fallback if Codeberg is unreachable
  • Only settings-repo-path: Original behavior; requires a local clone
  • Neither: Version check cannot run; a configuration warning is shown

File locations

PathRole
~/.claude/settings.jsonUser permissions allowlist, hooks (PreToolUse, UserPromptSubmit, PermissionRequest, Stop), optional mcpServers
~/.claude/hooks/block-write-commands.shHook script invoked for every Bash tool use before it runs
~/.claude/hooks/block-config-tool-writes.shHook script invoked for every Write/Edit/MultiEdit tool use before it runs
~/.claude/hooks/check-settings-version.shHook script that shows the status panel and warns on version mismatch
~/.claude/hooks/sound-notify.shOptional sound-notification wrapper. Reads ~/.claude/sound-config.sh and plays a sound on question / permission / stop events (v2.2.0+)
~/.claude/sound-config.shUser-editable, not chattr-locked. Absent by default (opt-in). Toggles sounds and points at sound files
~/.claude/settings-versionInstalled version (semver, matches repo VERSION)
~/.claude/settings-repo-urlCodeberg repo slug for online version checking (e.g. Conduction/.github)
~/.claude/settings-repo-pathAbsolute path to the root of the canonical repo (fallback for git-based check)
~/.claude/settings-repo-refBranch/tag/SHA to track for version checks (defaults to main)

Shape of ~/.claude/settings.json

1. permissions.deny

Hard-blocked patterns — Claude cannot perform these even with user approval:

  • Config files: Edit/Write of ~/.claude/settings.json, hooks/*, settings-version, settings-repo-path, settings-repo-url, settings-repo-ref
  • System: sudo, su, shutdown, reboot, halt, poweroff, mkfs, dd if=
  • GitHub destructive: gh pr merge, gh repo delete, gh release delete
  • Git destructive: git reset --hard, git clean -f/-fd/-fdx, git filter-branch, git filter-repo, git reflog expire/delete, git update-ref -d, git config --global, git checkout --, git restore -- (file restore; git restore --staged is allowed), git push --force/-f, git rebase
  • Filesystem destructive: rm -rf, rm -Rf
  • Package managers (arbitrary code execution): pip install, npm install

2. permissions.allow

Bash permission patterns granted without prompting. Keep this aligned with the hook: anything allowed here should still pass block-write-commands.sh, or the hook will deny the command even if it is allowlisted.

Allowed categories (all read-only; write operations are gated by the hook):

  • Inspection: ls, cat, head, tail, wc, stat, file, du, df, pwd, tree, find, realpath, basename, dirname
  • Text processing: diff, grep, egrep, tr, sort, jq, cut, uniq, column
  • System info: which, whoami, uname, ps, free, lsof, ss, id, groups, uptime, hostname, env, date
  • Checksums / misc: sha256sum, md5sum, nproc, printenv
  • Git (read-only): git log, git status, git diff, git show, git blame, git ls-files, git ls-tree, git rev-parse, git describe, git shortlog, git cat-file, git branch --list/-a/-v, git remote -v/show, git fetch, git stash list, git stash show, git config --list, git config --get
  • Navigation: cd
  • Docker (read): docker ps, docker images, docker image inspect, docker logs, docker inspect, docker stats, docker info, docker network ls/inspect, docker volume ls/inspect, docker --version, docker compose ps/config/logs/version
  • GitHub CLI (read): gh pr list/view/checks/diff, gh issue list/view, gh repo view, gh run list/view, gh release list/view, gh workflow list/view, gh auth status
  • Package managers (read): composer --version/show/validate/diagnose/audit/check-platform-reqs, node --version, npm --version/list/outdated/audit, pnpm list/outdated, yarn list, pip list/show/freeze
  • PHP: php -l/-m/-i/--version
  • Logs: Read(**/.claude/logs/**)

Not auto-approved (hook or user prompt required): curl, gh api, awk, git -C, broad git branch/git remote — these commands have dangerous modes that the hook cannot fully distinguish from safe usage. They go through the hook for write detection and prompt the user when needed.

Do not put broad Bash(*) allow rules here.

3. hooks.PreToolUse

"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/block-write-commands.sh" }]
}
]

4. hooks.UserPromptSubmit

"UserPromptSubmit": [
{
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/check-settings-version.sh" }]
}
]

5. hooks.PreToolUseAskUserQuestion matcher (sound wrapper)

{
"matcher": "AskUserQuestion",
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/sound-notify.sh question" }]
}

Fires when Claude uses the AskUserQuestion tool. Wrapper is silent unless the user has opted in — see Optional: notification sounds.

6. hooks.PermissionRequest

"PermissionRequest": [
{
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/sound-notify.sh permission" }]
}
]

Fires when Claude shows an allow/deny permission prompt. (v2.2.0 used Notification for this — empirical testing on Claude Code showed PermissionRequest is the actual event name; fixed in v2.2.2.)

7. hooks.Stop

"Stop": [
{
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/sound-notify.sh stop" }]
}
]

Fires when Claude finishes its turn.

8. mcpServers (optional)

7 Playwright browser instances (browser-1 through browser-7). browser-6 runs headed (no --headless). Adjust the count to match your actual usage.


What block-write-commands.sh does

  • Reads JSON from stdin once into a variable, then extracts cmd and transcript_path.
  • On deny, prints permissionDecision: "deny" JSON. On ask, prints permissionDecision: "ask" JSON. On allow, exits 0.
AreaAllowed silentlyPrompts for approvalHard blocked
curlAll curl write commands (data/output flags); canonical Codeberg raw URL passes when wrapped in a config-file write
gh apiAll gh api commands (not auto-approved)
git pushLast user message contains authorized phraseBlocked otherwise
git -CRead-only subcommandsWrite subcommands, branch/remote writespush (phrase-authorized)
git branch (bare)--list, -a, -v (auto-approved)-d/-D/-m/-M/-c/-C, --delete, --move, --copy
git remote (bare)-v, show (auto-approved)add, remove, rename, set-url, prune, update
envenv alone or VAR=valueUsing env to execute another command
dateDisplay time-s / --set (system clock)
catNormal stdoutShell redirection > / >>
findNormal traversal-delete, -exec, -execdir
sortNormal sort-o / --output, shell > / >>
awkAll awk commands (not auto-approved); system() and file output caught
hostnameRead hostnameSetting a new hostname (bare name argument)
rmAll rm commandsrm -rf / rm -Rf (deny-list)
lnAll ln commandsSymlinks/hardlinks to ~/.claude/
sed -iIn-place file editing
chownAll chown commands
installAll install commands
Pipe-to-shell| bash, | sh, base64 -d, eval
WSL boundaryAll paths/executables escaping the Linux filesystem
Config writes (~/.claude/)git show origin/main: from canonical repo; curl from canonical Codeberg raw URLAll other methods

Most guards use (^|[;&|]\s*)cmd\b patterns to catch commands both at the start of a line and when chained via &&, ;, or ||. The exception is the canonical-source check for config-file writes (Method 2 — Codeberg curl), which validates via a URL-prefix match rather than a segment-boundary anchor, and is additionally hardened by a decoy-detection check that rejects any non-canonical http(s) URL present in the same command.

Authorized git push phrases (case-insensitive): push for me, commit and push, please git push, push my changes.

What check-settings-version.sh does

  • Fires once per session (keyed to the transcript path via a flag file in $XDG_RUNTIME_DIR or ~/.claude/, with chmod 600).
  • Validates all config values from files before use — tracking_ref against ^[a-zA-Z0-9._/-]+$, online_repo_slug against ^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$, versions against ^[0-9]+\.[0-9]+\.[0-9]+$. Invalid values are refused with a warning.
  • Reads the installed version from ~/.claude/settings-version.
  • Reads the tracking ref from ~/.claude/settings-repo-ref (defaults to main when absent).
  • Online check (primary): If ~/.claude/settings-repo-url is set, fetches VERSION via curl from the Codeberg raw URL https://codeberg.org/<slug>/raw/branch/<ref>/global-settings/VERSION.
  • Git fetch (fallback): If the Codeberg method is not configured or fails, and ~/.claude/settings-repo-path points to a valid local clone, fetches via git fetch origin <ref> --depth=1 and reads the VERSION file from that ref.
  • Reads the local branch version from $REPO_DIR/global-settings/VERSION (if a local repo is configured).
  • Compares all versions using semver and prints a colored status panel to stderr (visible in the terminal/CLI).
  • Always injects a session-start message into Claude's context via stdout — "up to date", "update required", or "configuration error" — which Claude relays at the top of its first response.
  • Never silently skips: configuration issues (missing config files, unreachable remote, curl not installed) are shown in the panel and forwarded to Claude.

Optional: notification sounds (opt-in)

Since v2.2.0 the shared settings ship a lightweight wrapper (sound-notify.sh) that can play a sound on three events. It is silent by default — no sound plays until the user opts in by creating ~/.claude/sound-config.sh.

How it is wired

Three hook entries in settings.json invoke the same wrapper with different arguments:

EventWrapper argumentFires when
PreToolUse matcher AskUserQuestionquestionClaude asks you a multiple-choice question
PermissionRequestpermissionClaude shows an allow/deny permission prompt
StopstopClaude finishes its turn

What the wrapper does

  • Exits 0 immediately if the event arg is not one of question / permission / stop.
  • Exits 0 immediately if ~/.claude/sound-config.sh does not exist.
  • Sources the config file into whitelisted variables only — SOUND_ENABLED, SOUND_QUESTION_FILE, SOUND_PERMISSION_FILE, SOUND_STOP_FILE. It does not eval command strings.
  • If SOUND_ENABLED != 1, exits 0.
  • Detects an available player in order: paplayafplayaplaypowershell.exe (WSL).
  • Plays the sound in the background so the hook never blocks Claude's turn. Detaches the audio player from the hook's process group (setsid when available, otherwise nohup) so short sounds (<~500ms) survive Claude Code tearing down the hook — a bare & + disown is not enough. (Fixed in v2.2.1; observed in v2.2.0 as: complete.oga played, dialog-information.oga was silent.)
  • Always exits 0 — a broken sound never delays Claude.

Enabling sounds

# On minimal Linux / WSL2 Ubuntu installs the freedesktop sound theme isn't
# installed by default. Install it so the shipped defaults exist:
sudo apt install sound-theme-freedesktop

cp global-settings/sound-config.sh.example ~/.claude/sound-config.sh
${EDITOR:-nano} ~/.claude/sound-config.sh # flip SOUND_ENABLED=1

The example ships Linux paths as the default (freedesktop sound theme) — most likely to work for the standard Conduction WSL2 Ubuntu setup once the theme package is installed. macOS and WSL-Windows alternatives are commented in place if you need to switch. The sound-theme-freedesktop apt install is only needed if you keep the shipped Linux paths; point the SOUND_*_FILE variables at your own files and you can skip it.

Disabling sounds

# Flip the toggle:
sed -i 's/^SOUND_ENABLED=1/SOUND_ENABLED=0/' ~/.claude/sound-config.sh
# Or fully remove:
rm ~/.claude/sound-config.sh

What's blocked from Claude, what isn't

FileDeny-list ruleContent hookchattr +i
~/.claude/hooks/sound-notify.shEdit/Write(~/.claude/hooks/*)✅ matches hooks/? regex✅ applied on install
~/.claude/sound-config.sh❌ (intentional — user preference)❌ (not in protected list)❌ (not applied)

The wrapper is protected the same way every other hook script is — Claude cannot Edit or Write it, cannot use a Bash cat > ... trick to overwrite it, and the kernel refuses even root writes while the immutable bit is set.

The config file is intentionally not protected because:

  • It's a user preference file, not security policy. It never affects what commands Claude can run.
  • The wrapper reads only whitelisted file-path variables from it — it never evals command strings, so tampering with the file cannot produce shell injection.
  • Locking it would force a sudo chattr -i / +i dance for every sound tweak, defeating the point of a user-configurable feature.
  • The update-config skill uses Edit/Write to modify ~/.claude/ config on user request; keeping sound-config.sh unprotected lets that skill help users enable/tune sounds naturally.

If you personally want to prevent Claude from touching sound-config.sh in your local setup, add "Edit(~/.claude/sound-config.sh)" and "Write(~/.claude/sound-config.sh)" to a project .claude/settings.json deny list — that overlays on top of the global settings without needing a global settings change.

Relationship to this repo's .claude/settings.json

Project settings.json in .claude/ enables MCP servers and project-specific permissions. That is separate from the global Bash policy above:

  1. Global ~/.claude/settings.json + hooks for Bash safety and version checking.
  2. Project .claude/settings.json (and settings.local.json if used) for workspace-specific MCP.

Verification

After installing (see README), verify:

  • curl should prompt (not auto-approved)
  • find . -exec should prompt
  • rm -rf should be hard-blocked
  • Status panel appears at session start