Skip to content

settings.json Documentation

Scope. This document describes the Claude Code adapter's hook wiring file (settings.json / settings-unix.json). Other harnesses use their own native settings surfaces — see docs/harnesses/<harness>.md for each adapter's equivalent configuration entry point.

Overview

settings.json configures Claude Code hook execution for the apothem ecosystem. It declares which hooks fire on which events, their timeouts, and the top-level permission surface.

Two templates ship with the ecosystem — select exactly one based on the host shell:

Template Host Command form
settings.json PowerShell (any OS) src/apothem/hooks/lib/bootstrap.ps1 -Event <E> [-ContextFile <path>]
settings-unix.json Bash (any OS) bash src/apothem/hooks/lib/bootstrap.sh <E> [<path>]

Only one file may be active at a time — Claude Code loads ~/.claude/settings.json. To switch profiles, back up the active file and rename (or symlink) the other in its place.

Python-Only Runtime

Every configured hook command delegates to a small shell wrapper whose only job is to bootstrap Python. The pipeline is:

  1. Entry-point wrapper. The settings command invokes src/apothem/hooks/lib/bootstrap.ps1 -Event <E> (PowerShell) or bash src/apothem/hooks/lib/bootstrap.sh <E> (Bash). The wrapper resolves the ecosystem root from $CLAUDE_PROJECT_DIR (primary) or $LLM_PROJECT_DIR (vendor-neutral fallback), falling back to $HOME/.claude if neither is set or the project directory does not contain the wrapper.
  2. Interpreter locator. The wrapper sources the matching src/apothem/hooks/lib/find-python.ps1 or src/apothem/hooks/lib/find-python.sh. The locator discovers a real CPython 3.10+, rejecting the Microsoft Store shim by byte-size + WindowsApps path filter, and publishes the interpreter path.
  3. Python dispatch. The wrapper execs the located interpreter against src/apothem/hooks/dispatch.py --event-name <E> [--context-file <path>]. dispatch.py routes SessionStart to session_start_bootstrap.main() and every other whitelisted event to emit_hook_context.main(). Both always exit 0 and emit a valid JSON envelope on any failure.

Exactly four shell artifacts are permitted under src/apothem/hooks/lib/: bootstrap.ps1, bootstrap.sh, find-python.ps1, and find-python.sh. scripts/dev/validate_hooks.py::validate_python_hygiene enforces this — any other .ps1 or .sh file under src/apothem/hooks/ or scripts/ is a hard FAIL.

Hook Configuration Schema

Permissions

"permissions": {
  "allow": ["Read", "Glob", "Grep"]
}

Tools not in allow require explicit per-call approval or workspace-level permission. Read-only tools are pre-approved; write operations (Write, Edit, NotebookEdit, Bash) run through the PreToolUse validator.

Hook Events

Timeout values mirror the canonical block at CLAUDE.md §7.4 — the table below reproduces them for reader convenience; the canonical column governs.

Event Trigger Timeout Purpose Mandate
SessionStart New Claude Code session 30s Initialize context, read memory, check plan state CM-14
PreToolUse Before Write/Edit/NotebookEdit/Bash 10s Validate CM-7 compliance + frontmatter CM-7, CM-22
PreCompact Before context compaction 30s Externalize unexternalized state CM-24 §2.3
PostCompact After context compaction 30s Re-bootstrap from externalized state CM-24 §6.2
Stop Session exit 60s Session-end externalization CM-14/CM-22/CM-24/CM-26

dispatch.py additionally accepts UserPromptSubmit and Notification on its event whitelist. Neither is wired in the shipped templates; add a matcher block with the same locator + dispatch pattern to enable.

Top-Level Fields

Field Purpose Required
permissions Object with allow array listing pre-approved tool names (read-only tools by default) yes
hooks Map of event name → array of {matcher, hooks[]} blocks; see Hook Events below yes
model Host-resolvable model selector. Either an alias (e.g., "opus") or a full identifier (e.g., "claude-opus-4-7") is acceptable — the host resolves aliases to a full identifier at runtime. Both ship templates use the alias form for portability yes
defaultShell Default shell for tool Bash calls (powershell on Windows host, bash on Unix host) optional
autoUpdatesChannel Claude Code update channel (latest, stable) optional
effortLevel Default reasoning effort (low, medium, high, max) optional

Validation

Three validators cover the hook surface, in increasing rigor:

# Structural + Python-hygiene (fast, offline)
python scripts/dev/validate_hooks.py

# Full ecosystem (structure + frontmatter + delegated hook checks)
python scripts/dev/validate_ecosystem.py

# Adversarial sweep (storms every configured hook command with degraded inputs)
python scripts/dev/chaos_pass.py

All three must exit 0 before committing hook or settings changes.

Ad-hoc smoke test of a single event:

python src/apothem/hooks/dispatch.py --event-name SessionStart

Expected output: a single-line JSON envelope containing hookSpecificOutput.

Common Issues

SessionStart times out

Most commonly the find-python locator is discovering the Microsoft Store shim and failing to resolve a real interpreter. Verify:

python src/apothem/hooks/dispatch.py --event-name SessionStart

If the command prints a valid JSON envelope but the Claude Code panel shows a timeout, raise the event's timeout in settings.json. If the command fails at the shell level, install a real CPython 3.10+ and ensure it is on PATH or discoverable by the locator.

Every Write/Edit is blocked

PreToolUse is flagging plan-internal terms in content being written outside the harness install root (~/.claude/ for the Claude Code adapter). Review the content for CM-7 violations (see src/apothem/rules/persistent-conventions-vigilance.md). Domain language only for codebase artifacts.

settings.json not loaded

Claude Code reads ~/.claude/settings.json exclusively. To activate settings-unix.json, rename it (or symlink) to settings.json. Restart the session.

Hooks silently no-op

The hook contract is "always exit 0, always emit valid JSON." If the interpreter cannot be located the locator returns empty and the command becomes a no-op. Run chaos_pass.py to confirm every configured hook returns a valid envelope — it catches degraded resolution paths that a live session would hide.

Layered Precedence

Claude Code resolves the effective settings object by composing three layers in increasing order of precedence:

Tier Location Committed? Rationale
1. System Harness defaults compiled into Claude Code itself n/a (vendor-controlled) The floor; every operator inherits the same baseline.
2. Project <harness-root>/settings.json + <harness-root>/settings-unix.json ✅ Committed Project-shared conventions: hooks, permissions, MCP servers, statusline wiring. The apothem-source templates for these two files live at src/apothem/harnesses/claude_code/templates/.
3. User-local <harness-root>/settings.local.json (gitignored) — templated as settings.local.example.json at the repository root ❌ Gitignored Operator-specific overrides: API tokens, machine-local paths, opt-in features.

Higher-tier values override lower-tier values at the leaf-key level — when both the project and user-local layers declare a permissions.allow entry, the union is taken; when both declare a scalar like theme, the user-local value wins. The exact per-key merge semantics are owned by the Claude Code harness.

Creating a user-local override

  1. Copy settings.local.example.json (repository root) to <harness-root>/settings.local.json. The settings.local.json filename matches the .gitignore pattern, so the file never lands in version control.
  2. Edit the local copy — keep only the keys to override; the harness composes the layers, so absent keys inherit from the project layer.
  3. Verify the file parses as valid JSON before saving — the harness rejects malformed JSON at session start.