Skip to Content
AdvancedHooks Guide

Hooks Guide

Detailed guide to Claude Code’s Hooks system and MoAI-ADK’s default Hook scripts.

One-line summary: Hooks are Claude Code’s automatic reflex nerves. Automatically format files when saved, block dangerous commands.

What are Hooks?

Hooks are scripts that execute automatically in response to specific events in Claude Code.

To use the analogy of a doctor’s reflex test: when a knee is tapped (event occurs), the leg automatically rises (script executes), just as when Claude Code modifies a file (PostToolUse event), the formatter automatically runs (code cleanup).

Hook Event Types

Claude Code supports 10 event types.

Complete Event List

EventExecution TimingMain Purpose
SetupStart with --init, --init-only, --maintenance flagsInitial setup, environment checks
SessionStartWhen session startsProject info display, environment initialization
SessionEndWhen session endsCleanup, context storage
PreCompactBefore context compact (/clear etc)Backup important context
PreToolUseBefore tool useSecurity validation, block dangerous commands
PermissionRequestWhen permission dialog shownAuto allow/deny decisions
PostToolUseAfter tool useCode formatting, lint checks, LSP diagnostics
UserPromptSubmitWhen user submits promptPrompt preprocessing, validation
NotificationWhen Claude Code sends notificationCustomize desktop notifications
StopAfter response completesLoop control, completion condition check
SubagentStopAfter subagent work completesProcess subtask results

Event Details

1. Setup

Executed when Claude Code starts with --init, --init-only, or --maintenance flags. Used for initial setup and environment checks.

2. SessionStart

Executed when a session starts or resumes an existing session. Used for displaying project status and environment initialization.

3. SessionEnd

Executed when Claude Code session ends. Used for cleanup, context storage, and metrics collection.

4. PreCompact

Executed before Claude Code performs context compacting (like /clear command). Used to backup important context.

5. PreToolUse

Executed before a tool is called. Can block or modify tool calls. Used for security validation and blocking dangerous commands.

6. PermissionRequest

Executed when a permission dialog is displayed to the user. Can automatically allow or deny.

7. PostToolUse

Executed after a tool call completes. Used for code formatting, lint checks, and LSP diagnostics collection.

8. UserPromptSubmit

Executed when the user submits a prompt, before Claude processes it. Used for prompt preprocessing and validation.

9. Notification

Executed when Claude Code sends a notification. Can be customized for desktop notifications, sound alerts, etc.

10. Stop

Executed when Claude Code completes a response. Used for loop control and completion condition verification.

11. SubagentStop

Executed when a subagent’s work is complete. Used to process subtask results.

Events Implemented in MoAI-ADK

MoAI-ADK has implemented the following events:

EventStatusHook File
SessionStartsession_start__show_project_info.py
PreToolUsepre_tool__security_guard.py
PostToolUsepost_tool__code_formatter.py, post_tool__linter.py, post_tool__ast_grep_scan.py, post_tool__lsp_diagnostic.py
PreCompactpre_compact__save_context.py
SessionEndsession_end__auto_cleanup.py
Stopstop__loop_controller.py
SetupSee official examples
PermissionRequestSee official examples
UserPromptSubmitSee official examples
NotificationSee official examples
SubagentStopSee official examples

Event Execution Order

The order in which hooks execute during a typical file modification operation:

Claude Code Official Examples

These examples are standard patterns provided in Claude Code’s official documentation.

Bash Command Logging Hook

Logs all Bash commands to a log file.

{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt" } ] } ] } }

TypeScript Formatting Hook

Automatically runs Prettier after editing TypeScript files.

{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.ts$'; then npx prettier --write \"$file_path\"; fi; }" } ] } ] } }

Markdown Formatter Hook

Automatically detects and adds language tags to Markdown files.

{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_formatter.py" } ] } ] } }

.claude/hooks/markdown_formatter.py file:

#!/usr/bin/env python3 """ Markdown formatter for Claude Code output. Fixes missing language tags and spacing issues while preserving code content. """ import json import sys import re import os def detect_language(code): """Best-effort language detection from code content.""" s = code.strip() # JSON detection if re.search(r'^\\s*[{\\[]', s): try: json.loads(s) return 'json' except: pass # Python detection if re.search(r'^\\s*def\\s+\\w+\\s*\\(', s, re.M) or \ re.search(r'^\\s*(import|from)\\s+\\w+', s, re.M): return 'python' # JavaScript detection if re.search(r'\\b(function\\s+\\w+\\s*\\(|const\\s+\\w+\\s*=)', s) or \ re.search(r'=>|console\\.(log|error)', s): return 'javascript' # Bash detection if re.search(r'^#!.*\\b(bash|sh)\\b', s, re.M) or \ re.search(r'\\b(if|then|fi|for|in|do|done)\\b', s): return 'bash' return 'text' def format_markdown(content): """Format markdown content with language detection.""" # Fix unlabeled code fences def add_lang_to_fence(match): indent, info, body, closing = match.groups() if not info.strip(): lang = detect_language(body) return f"{indent}```{lang}\\n{body}{closing}\\n" return match.group(0) fence_pattern = r'(?ms)^([ \\t]{0,3})```([^\\n]*)\\n(.*?)(\\n\\1```)\\s*$' content = re.sub(fence_pattern, add_lang_to_fence, content) # Fix excessive blank lines content = re.sub(r'\\n{3,}', '\\n\\n', content) return content.rstrip() + '\\n' # Main execution try: input_data = json.load(sys.stdin) file_path = input_data.get('tool_input', {}).get('file_path', '') if not file_path.endswith(('.md', '.mdx')): sys.exit(0) # Not a markdown file if os.path.exists(file_path): with open(file_path, 'r', encoding='utf-8') as f: content = f.read() formatted = format_markdown(content) if formatted != content: with open(file_path, 'w', encoding='utf-8') as f: f.write(formatted) print(f"✓ Fixed markdown formatting in {file_path}") except Exception as e: print(f"Error formatting markdown: {e}", file=sys.stderr) sys.exit(1)

Desktop Notification Hook

Displays a desktop notification when Claude is waiting for input.

{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "notify-send 'Claude Code' 'Awaiting your input'" } ] } ] } }

File Protection Hook

Blocks modification of sensitive files.

{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "python3 -c \"import json, sys; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(p in path for p in ['.env', 'package-lock.json', '.git/']) else 0)\"" } ] } ] } }

MoAI Default Hooks

MoAI-ADK provides 11 default Hook scripts.

Hook List

Hook FileEventMatcherRoleTimeout
session_start__show_project_info.pySessionStartAllProject status display, update check5 sec
pre_tool__security_guard.pyPreToolUseWrite|Edit|BashBlock dangerous file modifications/commands5 sec
post_tool__code_formatter.pyPostToolUseWrite|EditAuto code formatting30 sec
post_tool__linter.pyPostToolUseWrite|EditAuto lint check60 sec
post_tool__ast_grep_scan.pyPostToolUseWrite|EditAST-based security scan30 sec
post_tool__lsp_diagnostic.pyPostToolUseWrite|EditLSP diagnostics collectiondefault
pre_compact__save_context.pyPreCompactAllSave context before /clear3 sec
session_end__auto_cleanup.pySessionEndAllSession end cleanup5 sec

| stop__loop_controller.py | Stop | All | Ralph loop control and completion check | default | | quality_gate_with_lsp.py | Manual | All | LSP-based quality gate validation | default |

SessionStart: Display Project Info

When a session starts, shows the current state of the project.

Displayed Information:

  • MoAI-ADK version and update status
  • Current project name and tech stack
  • Git branch, changes, last commit
  • Git strategy (Github-Flow mode, Auto Branch settings)
  • Language settings (conversation language)
  • Previous session context (SPEC status, task list)
  • Personalized welcome message or setup guide

PreToolUse: Security Guard

Protects dangerous operations before file modification/command execution.

Protected Files:

CategoryProtected FilesReason
Secret storagesecrets/, *.secrets.*, *.credentials.*Protect sensitive information
SSH keys~/.ssh/*, id_rsa*, id_ed25519*Protect server access keys
Certificates*.pem, *.key, *.crtProtect certificate files
Cloud credentials~/.aws/*, ~/.gcloud/*, ~/.azure/*, ~/.kube/*Protect cloud accounts
Git internal.git/*Git repository integrity
Token files*.token, .tokens/*, auth.jsonProtect auth tokens

Note: .env files are NOT protected. Allows developers to edit environment variables.

Blocking Behavior:

  • Detects Write/Edit attempts on protected files
  • Returns "permissionDecision": "deny" response in JSON format
  • Claude Code stops modifying that file

Dangerous Bash Command Blocking:

  • Database deletion: supabase db reset, neon database delete
  • Dangerous file deletion: rm -rf /, rm -rf .git
  • Docker complete removal: docker system prune -a
  • Force push: git push --force origin main
  • Terraform destroy: terraform destroy

PostToolUse: Code Formatter

Automatically cleans up code after file modification.

Supported Languages and Formatters:

LanguageFormatter (priority)Config File
Pythonruff format, blackpyproject.toml
TypeScript/JavaScriptbiome, prettier, eslint_d.prettierrc, biome.json
Gogofmt, goimportsdefault
Rustrustfmtrustfmt.toml
Rubyprettier.prettierrc
PHPprettier.prettierrc
Javaprettier.prettierrc
Kotlinprettier.prettierrc
Swiftswiftformat.swiftformat
C#prettier.prettierrc

Exclusions:

  • .json, .lock, .min.js, .svg, etc.
  • node_modules, .git, dist, build directories

PostToolUse: Linter

Automatically checks code quality after file modification.

Supported Languages and Linters:

LanguageLinter (priority)Check Items
Pythonruff check, flake8PEP 8, type hints, complexity
TypeScript/JavaScripteslint, biome lint, eslint_dCoding standards, potential bugs
Gogolangci-lintCode quality, performance
RustclippyRust idioms, performance

PostToolUse: AST-grep Scan

Scans for structural security vulnerabilities after file modification.

Supported Languages: Python, JavaScript/TypeScript, Go, Rust, Java, Kotlin, C/C++, Ruby, PHP

Sample Scan Patterns:

  • SQL Injection vulnerabilities (string-concatenated queries)
  • Hardcoded secret keys (API keys, tokens)
  • Unsafe function calls
  • Unused imports

Configuration: .claude/skills/moai-tool-ast-grep/rules/sgconfig.yml or sgconfig.yml at project root

PostToolUse: LSP Diagnostics

Collects LSP (Language Server Protocol) diagnostics after file modification.

Supported Languages: Python, TypeScript/JavaScript, Go, Rust, Java, Kotlin, Ruby, PHP, C/C++

Fallback Diagnostics: When LSP is unavailable, uses command-line tools:

  • Python: ruff check --output-format=json
  • TypeScript: tsc --noEmit

Configuration: .moai/config/sections/ralph.yaml

ralph: enabled: true hooks: post_tool_lsp: enabled: true severity_threshold: error # error | warning | info

PreCompact: Save Context

Saves current context to file before /clear execution.

Save Location: .moai/memory/context-snapshot.json

Saved Content:

  • Current active SPEC status (ID, phase, progress)
  • In-progress task list (TodoWrite)
  • Completed task list
  • Modified file list
  • Git status information (branch, uncommitted changes)
  • Key decisions

Archive: Previous snapshots are automatically archived to .moai/memory/context-archive/.

SessionEnd: Auto Cleanup

Performs the following tasks when session ends:

P0 Tasks (Required):

  • Save session metrics (files modified, commits made, SPECs worked on)
  • Save work status snapshot (.moai/memory/last-session-state.json)
  • Warning for uncommitted changes

P1 Tasks (Optional):

  • Cleanup temporary files (older than 7 days)
  • Cleanup cache files
  • Scan for root directory documentation violations
  • Generate session summary

Stop: Loop Controller

Controls Ralph Engine feedback loop.

Completion Condition Check:

  • LSP error count (0 errors goal)
  • LSP warning count
  • Test pass status
  • Coverage target (default 85%)
  • Completion markers (<moai>DONE</moai>, <moai>COMPLETE</moai>) detection

State File: .moai/cache/.moai_loop_state.json

Configuration: .moai/config/sections/ralph.yaml

ralph: enabled: true loop: max_iterations: 10 auto_fix: false completion: zero_errors: true zero_warnings: false tests_pass: true coverage_threshold: 85

Quality Gate with LSP

Validates quality gates using LSP diagnostics.

Quality Criteria:

  • Maximum error count: 0 (default)
  • Maximum warning count: 10 (default)
  • Type errors: 0 allowed
  • Lint errors: 0 allowed

Configuration: .moai/config/sections/quality.yaml

constitution: quality_gate: max_errors: 0 max_warnings: 10 enabled: true

Result Example:

{ "lsp_errors": 0, "lsp_warnings": 2, "type_errors": 0, "lint_errors": 0, "passed": true, "reason": "Quality gate passed: LSP diagnostics clean" }

lib/ Shared Library

MoAI Hooks provides modules in the lib/ directory for shared functionality.

.claude/hooks/moai/lib/ ├── __init__.py ├── atomic_write.py # Atomic write operations ├── checkpoint.py # Checkpoint management ├── common.py # Common utilities ├── config.py # Configuration management ├── config_manager.py # Configuration manager (advanced) ├── config_validator.py # Configuration validation ├── context_manager.py # Context management (snapshots, archives) ├── enhanced_output_style_detector.py # Output style detection ├── file_utils.py # File utilities ├── git_collector.py # Git data collection ├── git_operations_manager.py # Git operations manager (optimized) ├── language_detector.py # Language detection ├── language_validator.py # Language validation ├── main.py # Main entry point ├── memory_collector.py # Memory collection ├── metrics_tracker.py # Metrics tracking ├── models.py # Data models ├── path_utils.py # Path utilities ├── project.py # Project-related ├── renderer.py # Renderer ├── timeout.py # Timeout handling ├── tool_registry.py # Tool registry (formatters, linters) ├── unified_timeout_manager.py # Unified timeout manager ├── update_checker.py # Update check ├── version_reader.py # Version reading ├── alfred_detector.py # Alfred detection └── shared/utils/ └── announcement_translator.py # Announcement translation

Key Modules:

  • tool_registry.py: Auto-detection of formatters/linters for 16 programming languages
  • git_operations_manager.py: Optimized Git operations with connection pooling and caching
  • unified_timeout_manager.py: Unified timeout management with graceful degradation
  • context_manager.py: Context snapshots, archives, and Memory MCP payload generation

Hook Configuration in settings.json

Hooks are configured in the hooks section of the .claude/settings.json file.

{ "hooks": { "SessionStart": [ { "matcher": "", "hooks": [ { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/session_start__show_project_info.py\"'" } ] } ], "PreToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/pre_tool__security_guard.py\"'", "timeout": 5000 } ] } ], "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/post_tool__code_formatter.py\"'", "timeout": 30000 }, { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/post_tool__linter.py\"'", "timeout": 60000 }, { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/post_tool__ast_grep_scan.py\"'", "timeout": 30000 }, { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/post_tool__lsp_diagnostic.py\"'" } ] } ], "PreCompact": [ { "matcher": "", "hooks": [ { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/pre_compact__save_context.py\"'", "timeout": 5000 } ] } ], "SessionEnd": [ { "matcher": "", "hooks": [ { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/session_end__auto_cleanup.py\"'", "timeout": 5000 } ] } ], "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "${SHELL:-/bin/bash} -l -c 'uv run \"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/stop__loop_controller.py\"'" } ] } ] } }

Configuration Structure

FieldDescriptionExample
matcherTool name matching pattern (regex)"Write|Edit"
typeHook type"command"
commandCommand to executeShell script path
timeoutExecution time limit (milliseconds)5000 (5 seconds)

Matcher Patterns

PatternDescription
"" (empty string)Matches all tools
"Write"Matches only Write tool
"Write|Edit"Matches Write or Edit tools
"Bash"Matches only Bash tool

Writing Custom Hooks

Basic Template

Custom Hook scripts can be written in Python.

#!/usr/bin/env python3 """Custom PostToolUse Hook: Perform specific checks after file modification""" import json import sys def main(): # Read Hook input data from stdin input_data = json.loads(sys.stdin.read()) tool_name = input_data.get("tool_name", "") tool_input = input_data.get("tool_input", {}) file_path = tool_input.get("file_path", "") # Check logic if file_path.endswith(".py"): # Custom check for Python files result = check_python_file(file_path) if result["has_issues"]: # Send feedback to Claude Code output = { "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": result["message"] } } print(json.dumps(output)) return # Suppress output if no issues output = {"suppressOutput": True} print(json.dumps(output)) def check_python_file(file_path: str) -> dict: """Custom Python file check""" # Implement check logic return {"has_issues": False, "message": ""} if __name__ == "__main__": main()

Hook Response Format

FieldValueBehavior
suppressOutputtrueDisplay nothing
hookSpecificOutputobjectProvide additional context
permissionDecision"allow"Allow work (PreToolUse)
permissionDecision"deny"Block work (PreToolUse)
permissionDecision"ask"Request user confirmation (PreToolUse)

Hook Input Data

Hook scripts receive JSON data via standard input (stdin).

{ "tool_name": "Write", "tool_input": { "file_path": "/path/to/file.py", "content": "File content..." }, "tool_output": "File output result (PostToolUse only)" }

Hook Directory Structure

.claude/hooks/moai/ ├── __init__.py # Package initialization ├── session_start__show_project_info.py # Session start ├── pre_tool__security_guard.py # Security guard ├── post_tool__code_formatter.py # Code formatter ├── post_tool__linter.py # Linter ├── post_tool__ast_grep_scan.py # AST-grep scan ├── post_tool__lsp_diagnostic.py # LSP diagnostics ├── pre_compact__save_context.py # Context save ├── session_end__auto_cleanup.py # Auto cleanup ├── stop__loop_controller.py # Loop controller ├── quality_gate_with_lsp.py # Quality gate └── lib/ # Shared library ├── atomic_write.py # Atomic write ├── checkpoint.py # Checkpoint ├── common.py # Common utilities ├── config.py # Config ├── config_manager.py # Config manager ├── config_validator.py # Config validation ├── context_manager.py # Context management ├── git_operations_manager.py # Git operations manager ├── tool_registry.py # Tool registry ├── unified_timeout_manager.py # Timeout manager └── ... # Other modules

Caution: Setting hook timeouts too long will slow down Claude Code responses. Recommended: formatter 30 sec, linter 60 sec, security guard 5 sec or less.

Disabling Hooks with Environment Variables

Specific hooks can be disabled with environment variables:

HookEnvironment Variable
AST-grep scanMOAI_DISABLE_AST_GREP_SCAN=1
LSP diagnosticsMOAI_DISABLE_LSP_DIAGNOSTIC=1
Loop controllerMOAI_DISABLE_LOOP_CONTROLLER=1
export MOAI_DISABLE_AST_GREP_SCAN=1

Tip: Hooks are the core of MoAI-ADK quality assurance. Automating code formatting and lint checks allows developers to focus on logic. Add custom hooks to build automation tailored to your project.

Last updated on