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 — seedocs/harnesses/<harness>.mdfor 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:
- Entry-point wrapper. The settings command invokes
src/apothem/hooks/lib/bootstrap.ps1 -Event <E>(PowerShell) orbash 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/.claudeif neither is set or the project directory does not contain the wrapper. - Interpreter locator. The wrapper sources the matching
src/apothem/hooks/lib/find-python.ps1orsrc/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. - Python dispatch. The wrapper execs the located interpreter against
src/apothem/hooks/dispatch.py --event-name <E> [--context-file <path>].dispatch.pyroutesSessionStarttosession_start_bootstrap.main()and every other whitelisted event toemit_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¶
- Copy
settings.local.example.json(repository root) to<harness-root>/settings.local.json. Thesettings.local.jsonfilename matches the.gitignorepattern, so the file never lands in version control. - Edit the local copy — keep only the keys to override; the harness composes the layers, so absent keys inherit from the project layer.
- Verify the file parses as valid JSON before saving — the harness rejects malformed JSON at session start.