Hooks & Automation

Complete reference for Claude Code hooks, settings, MCP servers, and practical memory augmentation patterns.

Contents

All Hook Events

27 total events (source-verified from src/entrypoints/sdk/coreTypes.ts):

Events that CAN Block Actions (exit code 2)

EventWhen It FiresWhat It Blocks
PreToolUseAfter Claude creates tool params, before executionThe tool call
PermissionRequestWhen a permission prompt would be shownDenies permission
UserPromptSubmitWhen user submits a promptBlocks and erases prompt
StopWhen Claude is about to stopPrevents stopping
SubagentStopWhen a subagent is about to stopPrevents subagent stop
TaskCreatedWhen a task is createdRolls back creation
TaskCompletedWhen a task completesPrevents completion
ConfigChangeWhen config is modifiedBlocks config change
ElicitationWhen MCP elicitation is requestedDenies elicitation
WorktreeCreateWhen worktree creation is requestedFails creation
TeammateIdleWhen a teammate goes idleContinues teammate

Informational Events (cannot block)

EventWhen It Fires
PostToolUseAfter a tool executes successfully
PostToolUseFailureAfter a tool execution fails
SessionStartOn startup, resume, clear, or compact
SessionEndOn clear, resume, logout, exit
NotificationOn permission_prompt, idle_prompt, auth_success
SubagentStartWhen a subagent starts
CwdChangedWhen working directory changes
FileChangedWhen a watched file changes (by basename)
PreCompact / PostCompactBefore/after compaction
InstructionsLoadedWhen instruction files load
WorktreeRemoveWhen worktree is removed
SetupOn init or maintenance (matcher: init or maintenance)
PermissionDeniedAfter permission is denied (exit 2 enables retry)
ElicitationResultWhen MCP elicitation result arrives
StopFailureOn rate_limit, auth_failed, billing_error, etc.

Hook Handler Types

TypeCommunicationAsync?Default Timeout
commandstdin/stdout/exit codesYes (async: true)600s
httpHTTP POST / response bodyImplicit30s
promptstdin to LLM, JSON backNo30s
agentSubagent with toolsNo60s

Matcher Patterns

Matchers are regex patterns. What they match depends on the event:

EventMatches AgainstExamples
Tool eventstool_nameBash, Edit|Write, mcp__.*
SessionStartSession sourcestartup, resume, clear, compact
SessionEndEnd reasonclear, resume, logout
NotificationNotification typepermission_prompt, idle_prompt
StopFailureError typerate_limit, billing_error
InstructionsLoadedLoad reasonsession_start, compact

The if Field (source-verified)

Uses permission rule syntax for pre-execution filtering: "if": "Bash(git *)". Key differences from matcher:

Hook Input/Output

Input (stdin JSON)

All hooks receive a common envelope:

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/current/working/directory",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse"
}

Tool events additionally include tool_name, tool_input, and tool_use_id. PostToolUse also includes tool_response.

Output (stdout JSON, exit 0)

{
  "continue": true,
  "stopReason": "Build failed",
  "suppressOutput": false,
  "systemMessage": "Warning text",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow|deny|ask",
    "permissionDecisionReason": "explanation",
    "updatedInput": { "command": "modified command" },
    "additionalContext": "context for Claude"
  }
}

Exit Codes

CodeEffect
0Success. Stdout parsed for JSON output. Proceed.
2Block the action. Stderr shown as feedback to Claude.
OtherNon-blocking error. Stderr in verbose mode only.

Context Injection via Hooks

This is the key mechanism for memory augmentation For SessionStart and UserPromptSubmit hooks, anything in the additionalContext field is added to Claude's context. This is how you inject memory content that the harness wouldn't otherwise load.

SessionStart Output Fields (source-verified)

SessionStart hooks can return three special fields in hookSpecificOutput:

SessionStart: Inject Context at Session Start

{
  "hooks": {
    "SessionStart": [{
      "matcher": "startup",
      "hooks": [{
        "type": "command",
        "command": "/path/to/memory-router.sh",
        "timeout": 5000
      }]
    }]
  }
}

PostCompact: Re-inject After Compaction

{
  "hooks": {
    "SessionStart": [{
      "matcher": "compact",
      "hooks": [{
        "type": "command",
        "command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"Reminder: always verify claims before acting on them.\"}}'"
      }]
    }]
  }
}

Environment Variable Persistence

The $CLAUDE_ENV_FILE mechanism lets SessionStart hooks persist environment variables across the session:

#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export PROJECT_TYPE=svelte' >> "$CLAUDE_ENV_FILE"
fi

Key Settings Reference

Configuration Scopes (Precedence: high to low)

  1. Managed/etc/claude-code/managed-settings.json (cannot be overridden)
  2. CLI flags--model, --permission-mode, etc.
  3. Local.claude/settings.local.json (personal, not committed)
  4. Project.claude/settings.json (committed, shared)
  5. User~/.claude/settings.json (personal, all projects)

Memory-Related Settings (source-verified)

KeyPurpose
autoMemoryEnabledToggle auto memory (default: true when unset)
autoMemoryDirectoryCustom path (supports ~/). Ignored in projectSettings for security.
autoDreamEnabledUser override for dream consolidation scheduling
claudeMdExcludesGlob patterns to skip CLAUDE.md files in monorepos
includeGitInstructionsInclude built-in git instructions (default: true)
disableAllHooksKill switch for all hooks

Permission Rule Syntax

RuleEffect
BashMatches all Bash commands
Bash(npm run *)Commands starting with npm run
Read(./.env)Reading .env file
Edit(./src/**)Edits to any file under src/
WebFetch(domain:example.com)Fetches to example.com

MCP Servers for Memory

Configuration

// ~/.claude/.mcp.json (user scope)
{
  "mcpServers": {
    "memory": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-memory"],
      "env": { "MEMORY_DIR": "/home/visar/.claude/semantic-memory" }
    }
  }
}

Available Memory MCP Servers

ServerBackendFeatures
@modelcontextprotocol/server-memoryJSONL filesOfficial, simple, persistent KV store
mcp-memory-keeperFile-basedPersistent context management for Claude Code
mcp-memory-serviceKnowledge graphAutonomous consolidation, REST API
memorious-mcpChromaDB (local)Semantic search, store/recall/forget
mcp-server-qdrantQdrantOfficial Qdrant MCP, semantic memory

Tool definitions are deferred by default — only tool names consume context until used. Check per-server context costs with /mcp.

Path-Scoped Rules

The most reliable routing mechanism Path-scoped rules are mechanically enforced by the harness. They re-inject every time Claude accesses a matching file — unlike CLAUDE.md triggers which depend on Claude "remembering" to follow them.
# ~/.claude/rules/svelte-conventions.md
---
paths:
  - "**/+page.svelte"
  - "**/+layout.svelte"
  - "**/*.svelte"
---
# Svelte 5 Rules
- Never use $: reactive statements (Svelte 4 syntax)
- Use $state, $derived, $effect runes
- Never call imperative APIs inside $effect
- Read svelte5-pitfalls.md for full list
# ~/.claude/rules/deploy-safety.md
---
paths:
  - "**/deploy.sh"
  - "**/Dockerfile"
  - "**/.github/workflows/**"
---
# Deployment Rules
- Read hetzner-deploy.md before any deploy changes
- Always test locally before pushing

Practical Patterns

Pattern 1: Dynamic Git Context at Session Start

#!/bin/bash
# Inject git state as context
CONTEXT="Current branch: $(git branch --show-current 2>/dev/null)"
CONTEXT="$CONTEXT\nRecent commits:\n$(git log --oneline -5 2>/dev/null)"
CONTEXT="$CONTEXT\nChanged files:\n$(git diff --name-only 2>/dev/null)"
jq -n --arg ctx "$CONTEXT" '{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": $ctx
  }
}'

Pattern 2: Protect Files from Edits

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path')
PROTECTED=('.env' 'package-lock.json' '.git/')
for p in "${PROTECTED[@]}"; do
  if [[ "$FILE" == *"$p"* ]]; then
    echo "Blocked: $FILE matches protected pattern $p" >&2
    exit 2
  fi
done
exit 0

Pattern 3: Auto-format After File Writes

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path')
case "$FILE" in
  *.ts|*.tsx|*.js|*.jsx) npx prettier --write "$FILE" ;;
  *.py) black "$FILE" ;;
esac
exit 0

Pattern 4: Always-On Behavioral Rules via SessionStart

#!/bin/bash
# Inject universal rules that apply to every coding task
RULES="ALWAYS-ON RULES:
1. One fix at a time - only implement the specific approved change
2. Verify claims - label uncertain statements as 'Claim:' and offer to verify
3. Evidence first - debug from logs/data, never speculation
4. No magic strings - use constants or enums for property checks
5. Timebox debugging to ~1h, then build a minimal reproduction"

jq -n --arg ctx "$RULES" '{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": $ctx
  }
}'