МОДУЛЬ 06/УРОК

Хуки

Hooks

Hooks - это автоматические сценарии, которые выполняются в ответ на определённые события во время сеансов Claude Code. Они позволяют реализовать автоматизацию, валидацию, управление разрешениями и настраиваемые рабочие процессы.

Обзор

Hooks - это автоматические действия (shell-команды, HTTP-webhooks, LLM-промпты, вызовы MCP-инструментов или запуск subagent'ов), которые срабатывают автоматически при определённых событиях в Claude Code. Они принимают входные данные в формате JSON и возвращают результаты через коды завершения и JSON-вывод.

Ключевые возможности:

  • Автоматизация, управляемая событиями
  • Ввод/вывод в формате JSON
  • Поддержка типов hooks: command, http, mcp_tool, prompt и agent
  • Сопоставление по шаблонам для hooks, привязанных к конкретным инструментам

Конфигурация

Hooks настраиваются в файлах settings с определённой структурой:

  • ~/.claude/settings.json - пользовательские настройки (для всех проектов)
  • .claude/settings.json - настройки проекта (общие для команды, попадают в commit)
  • .claude/settings.local.json - локальные настройки проекта (не попадают в commit)
  • Managed policy - настройки уровня организации
  • hooks/hooks.json плагина - hooks в области плагина
  • Frontmatter Skill/Agent - hooks на время жизни компонента

Базовая структура конфигурации

json
{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Ключевые поля:

FieldDescriptionExample
matcherPattern to match tool names (case-sensitive)"Write", "Edit|Write", "*"
hooksArray of hook definitions[{ "type": "command", ... }]
typeHook type: "command" (bash), "prompt" (LLM), "http" (webhook), "mcp_tool" (MCP tool invocation, v2.1.118+), or "agent" (subagent)"command"
commandShell command to execute"$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"
timeoutOptional timeout in seconds (default 60)30
onceIf true, run the hook only once per sessiontrue

Шаблоны matcher

PatternDescriptionExample
Exact stringMatches specific tool"Write"
Regex patternMatches multiple tools"Edit|Write"
Comma-separatedMatches any listed tool (v2.1.191+)"Write,Edit"
WildcardMatches all tools"*" or ""
MCP toolsServer and tool pattern"mcp__memory__.*"

Матчеры сопоставляются точно (v2.1.195+). Идентификатор с дефисом (например, имя MCP-инструмента, содержащее дефис) больше не приводит к случайному совпадению как подстроки с другим инструментом. Матчеры со списком через запятую, например "Write,Edit", срабатывают для любого инструмента из списка - в более ранних сборках они молча не срабатывали никогда.

Значения матчера InstructionsLoaded:

Matcher ValueDescription
session_startInstructions loaded at session startup
nested_traversalInstructions loaded during nested directory traversal
path_glob_matchInstructions loaded via path glob pattern matching

Сужение через условия if (пути в аргументах инструмента)

Поле matcher выбирает hook по имени инструмента ("Write", "Edit|Write", "*"). Чтобы отфильтровать точнее - по аргументам инструмента, например запускать hook только когда правка затрагивает src/, или блокировать чтение секретных файлов, - добавьте условие if к конкретному обработчику hook. Это не то же самое, что matcher по имени инструмента: matcher определяет, какой инструмент, а if - какой именно вызов.

if использует синтаксис правил разрешений (ToolName(pattern)) и проверяется по имени инструмента вместе с его аргументами. Для Read/Edit/Write шаблон пути подчиняется семантике gitignore с теми же якорями, что и в правилах разрешений: простое имя вроде .env совпадает на любой глубине, src/** отсчитывается от текущего каталога, /src/** - от корня проекта, ~/... - от домашнего каталога, а //... - это абсолютный путь в файловой системе.

Поле if располагается на уровне обработчика hook - рядом с type и command внутри массива hooks, - а не на уровне matcher:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(src/**)",
            "command": "./hooks/lint-src.sh"
          }
        ]
      },
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "if": "Read(.env)",
            "command": "./hooks/block-secret-read.sh"
          }
        ]
      }
    ]
  }
}

Примеры допустимых шаблонов if: Edit(src/**) (правки внутри src/), Read(~/.ssh/**) (чтение любого SSH-ключа), Read(.env) (любой .env в текущей директории или ниже по дереву), Bash(git push *) (только подкоманды git push).

Обновление v2.1.214: односегментный шаблон dir/** в условии if у hook (например, Edit(src/**)) теперь совпадает только с <cwd>/dir - а не с одноимённой директорией на любой глубине дерева. Раньше src/** совпадал в том числе с foo/src/**. Если нужно совпадение на любой глубине, используйте **/dir/**. Важно: это сужение действует только для условий if: в hook и для автоодобрения по allow-правилам - правила запрета (deny) и запроса (ask) по-прежнему сопоставляются с dir/** на любой глубине.

Типы hook

Claude Code поддерживает пять типов hook:

Command Hooks

Тип hook по умолчанию. Выполняет shell-команду и обменивается данными через JSON по stdin/stdout, а также через коды возврата.

json
{
  "type": "command",
  "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
  "timeout": 60
}

Exec-форма (args)

Добавлено в v2.1.139.

Вместо shell-формы "command": "..." command hook может запускать бинарник напрямую через execve(), передавая массив args. Shell-парсинг при этом не выполняется, поэтому подстановки путей никогда не требуют экранирования кавычками, а конфигурация защищена от ошибок, связанных с shell-инъекциями.

json
{
  "type": "command",
  "args": ["python3", "$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py", "--strict"],
  "timeout": 60
}

Эти две формы взаимоисключающие - hook, в котором одновременно заданы command и args, отклоняется при загрузке конфигурации. Используйте command, когда нужны конвейеры, перенаправления, объединение команд через &amp;&amp; или подстановки shell; используйте args, когда вызываете один бинарник с аргументами.

HTTP Hooks

> Добавлено в v2.1.63.

Удалённые webhook-эндпоинты, которые получают тот же JSON на вход, что и command hooks. HTTP hooks отправляют JSON методом POST на указанный URL и получают JSON-ответ. Когда sandboxing включён, запросы HTTP hooks маршрутизируются через sandbox. Для интерполяции переменных окружения в URL необходимо в целях безопасности явно задать список allowedEnvVars.

json
{
  "hooks": {
    "PostToolUse": [{
      "type": "http",
      "url": "https://my-webhook.example.com/hook",
      "matcher": "Write"
    }]
  }
}

Ключевые свойства:

  • "type": "http" - обозначает hook как HTTP
  • "url" - URL endpoint webhook'а
  • Маршрутизируется через sandbox, если sandbox включён
  • Требует явно заданного списка allowedEnvVars для подстановки любых переменных окружения в URL

Prompt Hooks

Промпты, вычисляемые LLM: содержимое hook'а - это промпт, который обрабатывает Claude. Применяются преимущественно с событиями Stop и SubagentStop для интеллектуальной проверки завершения задачи.

json
{
  "type": "prompt",
  "prompt": "Evaluate if Claude completed all requested tasks.",
  "timeout": 30
}

LLM анализирует prompt и возвращает структурированное решение (подробнее см. Prompt-Based Hooks).

MCP Tool Hooks

Добавлено в v2.1.118.

Тип mcp_tool напрямую вызывает настроенный MCP-инструмент; в конфигурации указываются MCP-сервер и имя инструмента, а не shell-команда или URL. Это удобно, когда логика валидации или реакции уже реализована в одном из настроенных вами MCP-серверов.

json
{
  "matcher": "Edit",
  "hooks": [{
    "type": "mcp_tool",
    "server": "my-mcp-server",
    "tool": "validate_edit"
  }]
}

Ключевые свойства:

  • "type": "mcp_tool" - определяет этот hook как вызов MCP-инструмента
  • "server" - имя настроенного MCP-сервера
  • "tool" - имя вызываемого инструмента на этом сервере

Входные данные hook (имя инструмента, его входные параметры, контекст сессии) передаются как аргументы MCP-инструмента. О настройке MCP-серверов см. MCP server setup.

Agent Hooks

Проверочные hooks на основе субагентов: для оценки условий или выполнения сложных проверок запускается отдельный агент. В отличие от prompt hooks (одношаговая оценка через LLM), agent hooks могут использовать инструменты и выполнять многошаговые рассуждения.

json
{
  "type": "agent",
  "prompt": "Verify the code changes follow our architecture guidelines. Check the relevant design docs and compare.",
  "timeout": 120
}

Ключевые свойства:

  • "type": "agent" - обозначает, что это agent hook
  • "prompt" - описание задачи для субагента
  • Агент может использовать инструменты (Read, Grep, Bash и т. д.) для выполнения проверки
  • Возвращает структурированное решение, аналогичное prompt hooks

События hook

Claude Code поддерживает 31 событие hook:

EventWhen TriggeredMatcher InputCan BlockCommon Use
SessionStartSession begins/resumes/clear/compactstartup/resume/clear/compact/forkNoEnvironment setup
SetupInitial environment setup (one-time per session)(none)NoProvision tooling, install deps
InstructionsLoadedAfter CLAUDE.md or rules file loaded(none)NoModify/filter instructions
UserPromptSubmitUser submits prompt(none)YesValidate prompts
UserPromptExpansionUser prompt is expanded (e.g., @ mentions, slash commands resolved)(none)YesTransform or inspect expanded prompt
PreToolUseBefore tool executionTool nameYes (allow/deny/ask)Validate, modify inputs
PermissionRequestPermission dialog shownTool nameYesAuto-approve/deny
PermissionDeniedUser denies a permission promptTool nameNoLogging, analytics, policy enforcement
PostToolUseAfter tool succeedsTool nameNoAdd context, feedback
PostToolUseFailureTool execution failsTool nameNoError handling, logging
PostToolBatchAfter a batch of tool uses completes(none)NoAggregate reporting, batched validation
NotificationNotification sentNotification typeNoCustom notifications
MessageDisplayWhile assistant message text is displayed(none)NoTransform or hide displayed message text (v2.1.152)
SubagentStartSubagent spawnedAgent type nameNoSubagent setup
SubagentStopSubagent finishesAgent type nameYesSubagent validation
StopClaude finishes responding(none)YesTask completion check
StopFailureAPI error ends turn(none)NoError recovery, logging
TeammateIdleAgent team teammate idle(none)YesTeammate coordination
TaskCompletedTask marked complete(none)YesPost-task actions
TaskCreatedTask created via TaskCreate(none)NoTask tracking, logging
ConfigChangeConfig file changes(none)Yes (except policy)React to config updates
CwdChangedWorking directory changes(none)NoDirectory-specific setup
DirectoryAddedNew working directory registered mid-session via /add-dir or the SDK register_repo_root control request (v2.1.219)(none)NoSet up tooling for a newly added directory
FileChangedWatched file changes(none)NoFile monitoring, rebuild
PreCompactBefore context compactionmanual/autoNoPre-compact actions
PostCompactAfter compaction completes(none)NoPost-compact actions
WorktreeCreateWorktree being created(none)Yes (path return)Worktree initialization
WorktreeRemoveWorktree being removed(none)NoWorktree cleanup
ElicitationMCP server requests user input(none)YesInput validation
ElicitationResultUser responds to elicitation(none)YesResponse processing
SessionEndSession terminates(none)NoCleanup, final logging

Длительность в PostToolUse (v2.1.119): входные данные hook'ов PostToolUse и PostToolUseFailure теперь включают duration_ms - подробнее см. в разделе PostToolUse.

PreToolUse

Запускается после того, как Claude сформировал параметры инструмента, но до их обработки. Используйте этот hook для валидации или изменения входных параметров инструмента.

Конфигурация:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
          }
        ]
      }
    ]
  }
}

Часто используемые матчеры: Task, Bash, Glob, Grep, Read, Edit, Write, WebFetch, WebSearch

Управление выводом:

  • permissionDecision: "allow", "deny", "ask" или "defer"
    • "allow" пропускает запрос разрешения (кроме инструментов, требующих взаимодействия с пользователем, и инструментов-коннекторов, для которых ваша организация установила значение ask)
    • "deny" блокирует вызов инструмента
    • "ask" запрашивает подтверждение у пользователя
    • "defer" корректно завершает работу, чтобы инструмент можно было возобновить позже; при этом значении поля permissionDecisionReason, updatedInput и additionalContext игнорируются
    • Правила deny и ask всё равно применяются независимо от того, что вернул hook. Если несколько hook-ов PreToolUse дают противоречивые решения, приоритет следующий: deny > defer > ask > allow
  • permissionDecisionReason: обоснование решения. Для "allow" и "ask" показывается пользователю (но не Claude); для "deny" показывается Claude; для "defer" игнорируется
  • updatedInput: изменённые входные параметры инструмента

PostToolUse

Запускается сразу после завершения работы инструмента. Используется для проверки результатов, логирования или передачи дополнительного контекста обратно Claude.

Конфигурация:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py"
          }
        ]
      }
    ]
  }
}

Управление выводом:

  • решение "block" передаёт Claude обратную связь
  • additionalContext: контекст, добавляемый для Claude

Дополнительные поля ввода (v2.1.119):

FieldTypeDescription
duration_msnumberTool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hook execution. Available on both PostToolUse and PostToolUseFailure hooks.

Восстановимая блокировка (continueOnBlock, v2.1.139)

По умолчанию hook PostToolUse, возвращающий "decision": "block", прерывает текущий ход. Укажите у hook "continueOnBlock": true, чтобы вместо прерывания отказ возвращался Claude как tool_result - тогда модель сможет учесть этот отклик и повторить вызов или скорректировать действия.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/policy-check.py",
            "continueOnBlock": true
          }
        ]
      }
    ]
  }
}

Используйте это, когда reason у hook - это то, на что Claude может отреагировать (например, «этот файл доступен только для чтения; запиши в другое место»); не указывайте это, когда блокировка должна полностью прервать ход.

UserPromptSubmit

Срабатывает, когда пользователь отправляет prompt, до того как Claude начнёт его обрабатывать.

Конфигурация:

json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py"
          }
        ]
      }
    ]
  }
}

Управление выводом:

  • decision: "block" - прервать обработку
  • reason: причина блокировки
  • additionalContext: контекст, добавляемый к prompt

Stop и SubagentStop

Срабатывают, когда Claude завершает ответ (Stop) или когда завершает работу субагент (SubagentStop). Поддерживают проверку на основе prompt для интеллектуального определения факта завершения задачи.

Дополнительное поле ввода: hook'и Stop и SubagentStop получают в JSON-входе поле last_assistant_message, содержащее последнее сообщение Claude или субагента перед остановкой. Это удобно для оценки того, была ли задача выполнена.

Конфигурация:

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude completed all requested tasks.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

> Защитный лимит на последовательные блокировки (v2.1.143): Если Stop hook возвращает &quot;decision&quot;: &quot;block&quot; (или устанавливает continue: false) 8 раз подряд в рамках одного хода, Claude Code принудительно прерывает цикл и завершает сессию с предупреждением. Порог можно переопределить через переменную окружения CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=&lt;integer&gt; (значение 0 полностью отключает лимит). Это защищает от ситуации, когда сбойный Stop hook бесконечно зацикливает сессию.

Поле возврата (v2.1.163): Stop или SubagentStop hook может вернуть hookSpecificOutput.additionalContext, чтобы передать Claude обратную связь и продолжить ход, не показывая метку ошибки. Раньше повлиять на модель из Stop hook было неудобно; теперь hook может аккуратно внедрить контекст, избегая появления метки ошибки, характерной для прежних способов передачи обратной связи (например, &quot;decision&quot;: &quot;block&quot;).

json
{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Reminder: run the test suite before declaring done."
  }
}

SubagentStart

Срабатывает в момент запуска субагента. В matcher передаётся имя типа агента, что позволяет применять hooks к конкретным типам субагентов.

Конфигурация:

json
{
  "hooks": {
    "SubagentStart": [
      {
        "matcher": "code-review",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-init.sh"
          }
        ]
      }
    ]
  }
}

SessionStart

Запускается при старте или возобновлении сессии. Может сохранять переменные окружения.

Matchers: startup, resume, clear, compact, fork

Обновление v2.1.214: форкнутая сессия теперь возвращает источник "fork" - ранее возвращался "resume".

Особенность: используйте CLAUDE_ENV_FILE для сохранения переменных окружения (также доступно в hooks CwdChanged и FileChanged):

bash
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi
exit 0

Вывод на уровне сессии (v2.1.152): hook SessionStart может вернуть JSON для повторного сканирования skills и установки заголовка сессии:

json
{
  "reloadSkills": true,
  "hookSpecificOutput": {
    "sessionTitle": "Payments migration"
  }
}

Верхнеуровневый reloadSkills: true запускает повторное сканирование skills в текущей сессии (то же действие, что и команда /reload-skills), благодаря чему skills, только что установленные hook'ом, становятся доступны немедленно. hookSpecificOutput.sessionTitle задаёт отображаемый заголовок сессии при её запуске и возобновлении.

SessionEnd

Выполняется при завершении сессии - для очистки ресурсов или финальной записи в лог. Не может предотвратить завершение.

Значения поля Reason:

  • clear - пользователь очистил сессию
  • logout - пользователь вышел из аккаунта
  • prompt_input_exit - пользователь вышел через поле ввода промпта
  • other - иная причина

Конфигурация:

json
{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\""
          }
        ]
      }
    ]
  }
}

Событие уведомления

Обновлённые matchers для событий уведомлений:

  • permission_prompt - уведомление с запросом разрешения
  • idle_prompt - уведомление о простое
  • auth_success - успешная аутентификация
  • elicitation_dialog - диалог, отображаемый пользователю
  • agent_needs_input - фоновому agent требуется ввод (v2.1.198)
  • agent_completed - фоновый agent завершил работу (v2.1.198)

Hooks на уровне компонента

Hooks можно привязывать к конкретным компонентам (skills, agents, commands) через их frontmatter:

В SKILL.md, agent.md или command.md:

yaml
---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/check.sh"
          once: true  # Only run once per session
---

Поддерживаемые события для hooks в компонентах: PreToolUse, PostToolUse, Stop

Это позволяет определять hooks прямо в том компоненте, который их использует, благодаря чему связанный код хранится вместе.

Hooks во frontmatter субагента

Если во frontmatter субагента задан hook Stop, он автоматически преобразуется в hook SubagentStop, привязанный к этому субагенту. Это гарантирует, что stop hook срабатывает именно при завершении данного субагента, а не при остановке основной сессии.

yaml
---
name: code-review-agent
description: Automated code review subagent
hooks:
  Stop:
    - hooks:
        - type: prompt
          prompt: "Verify the code review is thorough and complete."
  # The above Stop hook auto-converts to SubagentStop for this subagent
---

Требуется доверие к рабочей области (v2.1.218): hooks во frontmatter project-субагента теперь требуют подтверждения доверия к рабочей области для папки, из которой был загружен файл агента, - иначе они не запустятся. До версии v2.1.218 такие hooks могли выполняться из папок, которым вы не давали доверие. О том, для каких scopes сделано исключение, см. документацию по субагентам.

Событие PermissionRequest

Обрабатывает запросы разрешений с пользовательским форматом вывода:

json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow|deny",
      "updatedInput": {},
      "message": "Custom message",
      "interrupt": false
    }
  }
}

Входные и выходные данные hook

JSON на входе (через stdin)

Все hooks получают JSON на вход через stdin:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/directory",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.js",
    "content": "..."
  },
  "tool_use_id": "toolu_01ABC123...",
  "agent_id": "agent-abc123",
  "agent_type": "main",
  "worktree": "/path/to/worktree",
  "effort": { "level": "medium" }
}

Общие поля:

FieldDescription
session_idUnique session identifier
transcript_pathPath to the conversation transcript file
cwdCurrent working directory
prompt_idUUID of the prompt being processed; correlates with the OpenTelemetry prompt.id attribute (v2.1.196)
hook_event_nameName of the event that triggered the hook
agent_idIdentifier of the agent running this hook
agent_typeType of agent ("main", subagent type name, etc.)
worktreePath to the git worktree, if the agent is running in one
effort.level(v2.1.133+) Active effort level: low, medium, high, xhigh, or max

Коды выхода

Exit CodeMeaningBehavior
0SuccessContinue, parse JSON stdout
2Blocking errorBlock operation, stderr shown as error
OtherNon-blocking errorContinue, stderr shown in verbose mode

JSON-вывод (stdout, код возврата 0)

json
{
  "continue": true,
  "stopReason": "Optional message if stopping",
  "suppressOutput": false,
  "systemMessage": "Optional warning message",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "File is in allowed directory",
    "updatedInput": {
      "file_path": "/modified/path.js"
    }
  }
}

Область применения (v2.1.121+): hookSpecificOutput.updatedToolOutput теперь учитывается для всех инструментов, а не только для MCP-инструментов. Hook PostToolUse, навешенный на Bash, Edit, Read и т. д., может переписать вывод инструмента до того, как его увидит Claude, - это удобно для маскирования секретов, нормализации diff-ов или отсеивания шумного вывода команд. Пример (удаление ANSI-кодов цвета из вывода Bash):

json
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "updatedToolOutput": "<plain-text output with ANSI escapes removed>"
  }
}

terminalSequence (v2.1.141)

Hook-и могут выдавать сырые OSC-последовательности (operating system command) - для этого в JSON-выводе задаётся поле terminalSequence. Когда hook возвращает результат, хост записывает эту последовательность в свой управляющий терминал. Это удобно для десктопных уведомлений, обновления заголовка окна и подачи звукового сигнала терминала (bell) - без необходимости держать собственный TTY.

FieldTypeDescription
terminalSequencestringRaw escape sequence (typically OSC 9 / OSC 0 / OSC 777). Written to the host terminal verbatim.
Пример - отправить desktop-уведомление через OSC 9 по завершении длительной задачи:
json
{
  "terminalSequence": "]9;Task complete"
}

Настройте это в hook Stop, чтобы уведомление появлялось, когда Claude завершает ответ. Поддержка escape-последовательностей зависит от терминала; Kitty/iTerm2/Windows Terminal поддерживают OSC 9.

Переменные окружения

VariableAvailabilityDescription
CLAUDE_PROJECT_DIRAll hooksAbsolute path to project root
CLAUDE_ENV_FILESessionStart, CwdChanged, FileChangedFile path for persisting env vars
CLAUDE_CODE_REMOTEAll hooks"true" if running in remote environments
${CLAUDE_PLUGIN_ROOT}Plugin hooksPath to plugin directory
${CLAUDE_PLUGIN_DATA}Plugin hooksPath to plugin data directory
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MSSessionEnd hooksConfigurable timeout in milliseconds for SessionEnd hooks (overrides default)
CLAUDE_CODE_SESSION_IDBash tool subprocesses (v2.1.132+)Session UUID; matches the session_id field in hook input JSON. Use to correlate bash logs with hook telemetry.
CLAUDE_EFFORTBash tool subprocesses (v2.1.133+)Active effort level (low/medium/high/xhigh/max); matches effort.level in hook input JSON.
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPProcess-wide (v2.1.143+)Max consecutive Stop-hook blocks before the session ends with a warning (default 8). Set to 0 to disable the cap.

Hooks на основе промптов

Для событий Stop и SubagentStop можно использовать проверку с помощью LLM:

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Review if all tasks are complete. Return your decision.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Текущая дата: вторник, 4 августа 2026 г.

<query>

Схема ответа LLM: </query>

json
{
  "decision": "approve",
  "reason": "All tasks completed successfully",
  "continue": false,
  "stopReason": "Task complete"
}

Примеры

Пример 1: Валидатор Bash-команд (PreToolUse)

Файл: .claude/hooks/validate-bash.py

python
#!/usr/bin/env python3
import json
import sys
import re

BLOCKED_PATTERNS = [
    (r"\brm\s+-rf\s+/", "Blocking dangerous rm -rf / command"),
    (r"\bsudo\s+rm", "Blocking sudo rm command"),
]

def main():
    input_data = json.load(sys.stdin)

    tool_name = input_data.get("tool_name", "")
    if tool_name != "Bash":
        sys.exit(0)

    command = input_data.get("tool_input", {}).get("command", "")

    for pattern, message in BLOCKED_PATTERNS:
        if re.search(pattern, command):
            print(message, file=sys.stderr)
            sys.exit(2)  # Exit 2 = blocking error

    sys.exit(0)

if __name__ == "__main__":
    main()

Текущая дата: вторник, 4 августа 2026 г.

<query>

Конфигурация: </query>

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\""
          }
        ]
      }
    ]
  }
}

Пример 2: сканер безопасности (PostToolUse)

Файл: .claude/hooks/security-scan.py

python
#!/usr/bin/env python3
import json
import sys
import re

SECRET_PATTERNS = [
    (r"password\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded password"),
    (r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded API key"),
]

def main():
    input_data = json.load(sys.stdin)

    tool_name = input_data.get("tool_name", "")
    if tool_name not in ["Write", "Edit"]:
        sys.exit(0)

    tool_input = input_data.get("tool_input", {})
    content = tool_input.get("content", "") or tool_input.get("new_string", "")
    file_path = tool_input.get("file_path", "")

    warnings = []
    for pattern, message in SECRET_PATTERNS:
        if re.search(pattern, content, re.IGNORECASE):
            warnings.append(message)

    if warnings:
        output = {
            "hookSpecificOutput": {
                "hookEventName": "PostToolUse",
                "additionalContext": f"Security warnings for {file_path}: " + "; ".join(warnings)
            }
        }
        print(json.dumps(output))

    sys.exit(0)

if __name__ == "__main__":
    main()

Пример 3. Автоформатирование кода (PostToolUse)

Файл: .claude/hooks/format-code.sh

bash
#!/bin/bash

# Read JSON from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")

if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
    exit 0
fi

# Format based on file extension
case "$FILE_PATH" in
    *.js|*.jsx|*.ts|*.tsx|*.json)
        command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null
        ;;
    *.py)
        command -v black &>/dev/null && black "$FILE_PATH" 2>/dev/null
        ;;
    *.go)
        command -v gofmt &>/dev/null && gofmt -w "$FILE_PATH" 2>/dev/null
        ;;
esac

exit 0

Пример 4: валидатор промпта (UserPromptSubmit)

Файл: .claude/hooks/validate-prompt.py

python
#!/usr/bin/env python3
import json
import sys
import re

BLOCKED_PATTERNS = [
    (r"delete\s+(all\s+)?database", "Dangerous: database deletion"),
    (r"rm\s+-rf\s+/", "Dangerous: root deletion"),
]

def main():
    input_data = json.load(sys.stdin)
    prompt = input_data.get("user_prompt", "") or input_data.get("prompt", "")

    for pattern, message in BLOCKED_PATTERNS:
        if re.search(pattern, prompt, re.IGNORECASE):
            output = {
                "decision": "block",
                "reason": f"Blocked: {message}"
            }
            print(json.dumps(output))
            sys.exit(0)

    sys.exit(0)

if __name__ == "__main__":
    main()

Пример 5: Интеллектуальный Stop Hook (на основе промпта)

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Review if Claude completed all requested tasks. Check: 1) Were all files created/modified? 2) Were there unresolved errors? If incomplete, explain what's missing.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Пример 6: Трекер использования контекста (пары hook'ов)

Отслеживайте расход токенов на каждый запрос с помощью связки hook'ов UserPromptSubmit (перед сообщением) и Stop (после ответа).

Файл: .claude/hooks/context-tracker.py

python
#!/usr/bin/env python3
"""
Context Usage Tracker - Tracks token consumption per request.

Uses UserPromptSubmit as "pre-message" hook and Stop as "post-response" hook
to calculate the delta in token usage for each request.

Token Counting Methods:
1. Character estimation (default): ~4 chars per token, no dependencies
2. tiktoken (optional): More accurate (~90-95%), requires: pip install tiktoken
"""
import json
import os
import sys
import tempfile

# Configuration
CONTEXT_LIMIT = 128000  # Claude's context window (adjust for your model)
USE_TIKTOKEN = False    # Set True if tiktoken is installed for better accuracy


def get_state_file(session_id: str) -> str:
    """Get temp file path for storing pre-message token count, isolated by session."""
    return os.path.join(tempfile.gettempdir(), f"claude-context-{session_id}.json")


def count_tokens(text: str) -> int:
    """
    Count tokens in text.

    Uses tiktoken with p50k_base encoding if available (~90-95% accuracy),
    otherwise falls back to character estimation (~80-90% accuracy).
    """
    if USE_TIKTOKEN:
        try:
            import tiktoken
            enc = tiktoken.get_encoding("p50k_base")
            return len(enc.encode(text))
        except ImportError:
            pass  # Fall back to estimation

    # Character-based estimation: ~4 characters per token for English
    return len(text) // 4


def read_transcript(transcript_path: str) -> str:
    """Read and concatenate all content from transcript file."""
    if not transcript_path or not os.path.exists(transcript_path):
        return ""

    content = []
    with open(transcript_path, "r") as f:
        for line in f:
            try:
                entry = json.loads(line.strip())
                # Extract text content from various message formats
                if "message" in entry:
                    msg = entry["message"]
                    if isinstance(msg.get("content"), str):
                        content.append(msg["content"])
                    elif isinstance(msg.get("content"), list):
                        for block in msg["content"]:
                            if isinstance(block, dict) and block.get("type") == "text":
                                content.append(block.get("text", ""))
            except json.JSONDecodeError:
                continue

    return "\n".join(content)


def handle_user_prompt_submit(data: dict) -> None:
    """Pre-message hook: Save current token count before request."""
    session_id = data.get("session_id", "unknown")
    transcript_path = data.get("transcript_path", "")

    transcript_content = read_transcript(transcript_path)
    current_tokens = count_tokens(transcript_content)

    # Save to temp file for later comparison
    state_file = get_state_file(session_id)
    with open(state_file, "w") as f:
        json.dump({"pre_tokens": current_tokens}, f)


def handle_stop(data: dict) -> None:
    """Post-response hook: Calculate and report token delta."""
    session_id = data.get("session_id", "unknown")
    transcript_path = data.get("transcript_path", "")

    transcript_content = read_transcript(transcript_path)
    current_tokens = count_tokens(transcript_content)

    # Load pre-message count
    state_file = get_state_file(session_id)
    pre_tokens = 0
    if os.path.exists(state_file):
        try:
            with open(state_file, "r") as f:
                state = json.load(f)
                pre_tokens = state.get("pre_tokens", 0)
        except (json.JSONDecodeError, IOError):
            pass

    # Calculate delta
    delta_tokens = current_tokens - pre_tokens
    remaining = CONTEXT_LIMIT - current_tokens
    percentage = (current_tokens / CONTEXT_LIMIT) * 100

    # Report usage
    method = "tiktoken" if USE_TIKTOKEN else "estimated"
    print(f"Context ({method}): ~{current_tokens:,} tokens ({percentage:.1f}% used, ~{remaining:,} remaining)", file=sys.stderr)
    if delta_tokens > 0:
        print(f"This request: ~{delta_tokens:,} tokens", file=sys.stderr)


def main():
    data = json.load(sys.stdin)
    event = data.get("hook_event_name", "")

    if event == "UserPromptSubmit":
        handle_user_prompt_submit(data)
    elif event == "Stop":
        handle_stop(data)

    sys.exit(0)


if __name__ == "__main__":
    main()

Текущая дата: вторник, 4 августа 2026 г.

<query>

Конфигурация: </query>

json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
          }
        ]
      }
    ]
  }
}

Как это работает:

  1. UserPromptSubmit срабатывает до обработки вашего промпта - сохраняет текущее число токенов
  2. Stop срабатывает после ответа Claude - вычисляет разницу и выводит статистику использования
  3. Сессии изолируются друг от друга через session_id в имени временного файла

Методы подсчёта токенов:

MethodAccuracyDependenciesSpeed
Character estimation~80-90%None<1ms
tiktoken (p50k_base)~90-95%pip install tiktoken<10ms

Примечание: Anthropic не выпускала официального офлайн-токенизатора. Оба метода дают лишь приблизительный результат. В транскрипт попадают запросы пользователя, ответы Claude и вывод инструментов, но НЕ системные промпты и не внутренний контекст.

Пример 7: предзаполнение разрешений для auto-mode (одноразовый скрипт настройки)

Одноразовый скрипт настройки, который добавляет в ~/.claude/settings.json около 67 безопасных правил разрешений, соответствующих базовому набору auto-mode в Claude Code - без каких-либо hook'ов и без запоминания будущих решений. Запустите его один раз; повторный запуск безопасен (уже существующие правила пропускаются).

Файл: 09-advanced-features/setup-auto-mode-permissions.py

bash
# Preview what would be added
python3 09-advanced-features/setup-auto-mode-permissions.py --dry-run

# Apply
python3 09-advanced-features/setup-auto-mode-permissions.py

Что добавляется:

CategoryExamples
Built-in toolsRead(*), Edit(*), Write(*), Glob(*), Grep(*), Agent(*), WebSearch(*)
Git readBash(git status:*), Bash(git log:*), Bash(git diff:*)
Git write (local)Bash(git add:*), Bash(git commit:*), Bash(git checkout:*)
Package managersBash(npm install:*), Bash(pip install:*), Bash(cargo build:*)
Build & testBash(make:*), Bash(pytest:*), Bash(go test:*)
Common shellBash(ls:*), Bash(cat:*), Bash(find:*), Bash(cp:*), Bash(mv:*)
GitHub CLIBash(gh pr view:*), Bash(gh pr create:*), Bash(gh issue list:*)
Что намеренно исключено (никогда не добавляется этим скриптом):
  • rm -rf, sudo, force push, git reset --hard
  • DROP TABLE, kubectl delete, terraform destroy
  • npm publish, curl | bash, деплой в production

Пример 8: логирование прогресса обучения (SessionEnd)

Записывайте, какие модули вы изучили, в конце каждой сессии Claude Code. Прогресс хранится в ~/.claude-howto-progress.json - вне репозитория, поэтому он переживает git pull без перезаписи.

Почему SessionEnd, а не Stop? Stop срабатывает после каждого ответа Claude. SessionEnd срабатывает один раз - при завершении сессии, что как раз и нужно для итоговой записи в дневник.

Почему /dev/tty для ввода? Hook-скрипты получают JSON-payload хука через stdin, поэтому интерактивный read должен обращаться напрямую к /dev/tty, чтобы достучаться до терминала.

Файл: 06-hooks/session-end.sh

bash
#!/usr/bin/env bash
# SessionEnd hook: prompts for modules worked on, then appends a session record
# to ~/.claude-howto-progress.json for persistent learning progress tracking.

PROGRESS_FILE="$HOME/.claude-howto-progress.json"

# Guard: only run inside this repo
if [[ "$CLAUDE_PROJECT_DIR" != *"claude-howto"* ]] && [[ "$PWD" != *"claude-howto"* ]]; then
  exit 0
fi

if [ ! -f "$PROGRESS_FILE" ]; then
  echo '{"sessions":[]}' > "$PROGRESS_FILE"
fi

DATE=$(date +"%Y-%m-%d")
TIME=$(date +"%H:%M")

echo ""
echo " Which modules did you work on? (e.g. 06,07 or press Enter to skip)"
echo " 01=Slash  02=Memory  03=Skills  04=Subagents  05=MCP"
echo " 06=Hooks  07=Plugins 08=Checkpoints 09=Advanced 10=CLI"
printf " > "
read -r INPUT </dev/tty

if [ -z "$INPUT" ] || [ "$INPUT" = "skip" ]; then
  exit 0
fi

MODULES_JSON=$(echo "$INPUT" | tr ',' '\n' | tr -d ' ' | while read -r m; do
  case "$m" in
    01) echo '"01-slash-commands"' ;;
    02) echo '"02-memory"' ;;
    03) echo '"03-skills"' ;;
    04) echo '"04-subagents"' ;;
    05) echo '"05-mcp"' ;;
    06) echo '"06-hooks"' ;;
    07) echo '"07-plugins"' ;;
    08) echo '"08-checkpoints"' ;;
    09) echo '"09-advanced-features"' ;;
    10) echo '"10-cli"' ;;
    *)  echo "\"$m\"" ;;
  esac
done | paste -sd ',' -)

printf " Notes? (optional, press Enter to skip): "
read -r NOTES </dev/tty

# Pass NOTES as a separate argument so Python handles JSON escaping -
# avoids broken JSON when notes contain quotes or backslashes.
python3 - "$PROGRESS_FILE" "$DATE" "$TIME" "$MODULES_JSON" "$NOTES" <<'PYEOF'
import sys, json

path, date, time_str, modules_raw, notes = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]

new_session = {
    "date": date,
    "time": time_str,
    "modules": json.loads(f"[{modules_raw}]") if modules_raw else [],
    "notes": notes,
}

with open(path, 'r') as f:
    data = json.load(f)

data.setdefault('sessions', []).append(new_session)

with open(path, 'w') as f:
    json.dump(data, f, indent=2)
PYEOF

echo " Saved to $PROGRESS_FILE"

Установка - скопируйте скрипт в директорию hook'ов проекта, чтобы путь в settings.json корректно разрешался:

bash
mkdir -p .claude/hooks
cp 06-hooks/session-end.sh .claude/hooks/
chmod +x .claude/hooks/session-end.sh

Конфигурация.claude/settings.json):

json
{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-end.sh\""
          }
        ]
      }
    ]
  }
}

Вывод - ~/.claude-howto-progress.json:

json
{
  "sessions": [
    {
      "date": "2026-04-18",
      "time": "14:32",
      "modules": ["06-hooks", "07-plugins"],
      "notes": "Installed first hook, tried pre-commit example"
    }
  ]
}

Ключевые демонстрируемые паттерны:

PatternWhy it matters
SessionEnd eventFires once on exit - not after every response like Stop
read -r INPUT </dev/ttyHooks own stdin (JSON payload); use /dev/tty for user input
$CLAUDE_PROJECT_DIRPortable path - never hardcode /Users/yourname/...
Guard clause at topPrevents the hook running in unrelated projects if installed globally
Store outside the repo~/ path survives git pull without overwriting your data
Дополнение: визуальный трекер прогресса

Чтобы получить полноценный интерфейс с чекбоксами по всем 10 модулям, откройте прилагаемый трекер в браузере:

bash
open local-progress/index.html

Прогресс хранится в браузерном localStorage (в репозиторий на диск ничего не пишется). Нажмите Export, чтобы сохранить снимок состояния в виде JSON, и Import - чтобы восстановить его.

Hooks плагинов

Плагины могут объявлять hooks в файле hooks/hooks.json:

Файл: plugins/hooks/hooks.json

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
          }
        ]
      }
    ]
  }
}

Переменные окружения в hooks плагинов:

  • ${CLAUDE_PLUGIN_ROOT} - путь к директории плагина
  • ${CLAUDE_PLUGIN_DATA} - путь к директории данных плагина

Это позволяет плагинам подключать собственные hooks для валидации и автоматизации.

Hooks для инструментов MCP

Инструменты MCP соответствуют шаблону mcp__<server>__<tool>:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"systemMessage\": \"Memory operation logged\"}'"
          }
        ]
      }
    ]
  }
}

Вопросы безопасности

Отказ от ответственности

ИСПОЛЬЗУЙТЕ НА СВОЙ СТРАХ И РИСК: hook’и выполняют произвольные shell-команды. Вы несёте полную ответственность за:

  • команды, которые вы настраиваете;
  • права доступа к файлам и их изменения;
  • возможную потерю данных или повреждение системы;
  • тестирование hook’ов в безопасных окружениях перед использованием в production.

Замечания по безопасности

  • Требуется доверие к рабочей области: команды, выводимые hook’ами statusLine и fileSuggestion, теперь вступают в силу только после подтверждения доверия к рабочей области.
  • Размер терминала для status-line (v2.1.153): скрипты команд status-line теперь получают переменные окружения COLUMNS и LINES, что позволяет адаптировать вывод под ширину/высоту терминала (например, [ "$COLUMNS" -lt 80 ] && short_output).
  • HTTP-hook’и и переменные окружения: для интерполяции переменных окружения в URL HTTP-hook’и требуют явного указания списка allowedEnvVars. Это предотвращает случайную утечку чувствительных переменных окружения на удалённые endpoint’ы.
  • Иерархия управляемых настроек: параметр disableAllHooks теперь учитывает иерархию управляемых настроек - это значит, что настройки уровня организации могут принудительно отключать hook’и без возможности переопределения со стороны отдельных пользователей.
  • Автоодобрение PowerShell (v2.1.119): команды инструмента PowerShell теперь могут автоматически одобряться в режиме разрешений - аналогично Bash. Это уравнивает возможности для пользователей Windows, работающих в Claude Code с shell-инструментами на базе PowerShell.
  • Закрыта лазейка с автоодобрением «голых» env-var в Bash (v2.1.145): до версии v2.1.145 Bash-команда вида FOO=bar somecommand (встроенное присваивание переменной вместе с командой, отсутствующей в allowlist) могла быть автоматически одобрена, если в allowlist находилось только само FOO=bar. В v2.1.145 эта лазейка закрыта - такие команды теперь вызывают запрос разрешения. Скрипты, полагавшиеся на неявное разрешение, начнут запрашивать подтверждение; чтобы вернуть автоодобрение, явно добавьте правило разрешения Bash(...), охватывающее команду целиком, а не только присваивание переменной.

Лучшие практики

DoDon't
Validate and sanitize all inputsTrust input data blindly
Quote shell variables: "$VAR"Use unquoted: $VAR
Block path traversal (..)Allow arbitrary paths
Use absolute paths with $CLAUDE_PROJECT_DIRHardcode paths
Skip sensitive files (.env, .git/, keys)Process all files
Test hooks in isolation firstDeploy untested hooks
Use explicit allowedEnvVars for HTTP hooksExpose all env vars to webhooks

Отладка

Включение режима отладки

Запустите Claude с флагом отладки, чтобы получить подробные логи hook'ов:

bash
claude --debug

Подробный режим

Нажмите Ctrl+O в Claude Code, чтобы включить подробный режим и отслеживать ход выполнения hook'ов.

Тестирование hook'ов по отдельности

bash
# Test with sample JSON input
echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python3 .claude/hooks/validate-bash.py

# Check exit code
echo $?

Полный пример конфигурации

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"",
            "timeout": 10
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\"",
            "timeout": 30
          },
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py\"",
            "timeout": 10
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py\""
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-init.sh\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Verify all tasks are complete before stopping.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Детали выполнения hook

AspectBehavior
Timeout60 seconds default, configurable per command
ParallelizationAll matching hooks run in parallel
DeduplicationIdentical hook commands deduplicated
EnvironmentRuns in current directory with Claude Code's environment

Устранение неполадок

Hook не выполняется

  • Убедитесь, что синтаксис JSON-конфигурации корректен
  • Проверьте, что паттерн matcher соответствует имени инструмента
  • Убедитесь, что скрипт существует и имеет права на выполнение: chmod +x script.sh
  • Запустите claude --debug, чтобы увидеть логи выполнения hook
  • Убедитесь, что hook читает JSON из stdin (а не из аргументов команды)

Hook неожиданно блокирует выполнение

  • Протестируйте hook на примере JSON: echo '{"tool_name": "Write", ...}' | ./hook.py
  • Проверьте exit code: 0 - разрешить, 2 - заблокировать
  • Проверьте вывод в stderr (отображается при exit code 2)

Ошибки парсинга JSON

  • Всегда читайте из stdin, а не из аргументов командной строки
  • Используйте полноценный парсер JSON (а не манипуляции со строками)
  • Корректно обрабатывайте отсутствующие поля

Установка

Шаг 1. Создайте директорию для hooks

bash
mkdir -p ~/.claude/hooks

Шаг 2: Скопируйте примеры hooks

bash
cp 06-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh

Шаг 3: Настройка в settings

Отредактируйте ~/.claude/settings.json или .claude/settings.json, добавив конфигурацию hook, показанную выше.

Связанные концепции

  • Checkpoints и Rewind - сохранение и восстановление состояния диалога
  • Slash-команды - создание пользовательских slash-команд
  • Skills - переиспользуемые автономные возможности
  • Subagents - делегированное выполнение задач
  • Plugins - упакованные комплекты расширений
  • Продвинутые возможности - знакомство с продвинутыми возможностями Claude Code

Дополнительные материалы


Последнее обновление: 4 августа 2026 г. Версия Claude Code: 2.1.220 Источники:

Совместимые модели: Claude Fable 5, Claude Opus 5, Claude Sonnet 5, Claude Sonnet 4.6, Claude Opus 4.8, Claude Haiku 4.5

ЛОКАЛЬНАЯ ОТМЕТКА · БЕЗ ПРОВЕРКИ
cc-learnМОДУЛЬ 06
МОДУЛЬ 06/УРОК

Хуки

Hooks

Hooks - это автоматические сценарии, которые выполняются в ответ на определённые события во время сеансов Claude Code. Они позволяют реализовать автоматизацию, валидацию, управление разрешениями и настраиваемые рабочие процессы.

Обзор

Hooks - это автоматические действия (shell-команды, HTTP-webhooks, LLM-промпты, вызовы MCP-инструментов или запуск subagent'ов), которые срабатывают автоматически при определённых событиях в Claude Code. Они принимают входные данные в формате JSON и возвращают результаты через коды завершения и JSON-вывод.

Ключевые возможности:

  • Автоматизация, управляемая событиями
  • Ввод/вывод в формате JSON
  • Поддержка типов hooks: command, http, mcp_tool, prompt и agent
  • Сопоставление по шаблонам для hooks, привязанных к конкретным инструментам

Конфигурация

Hooks настраиваются в файлах settings с определённой структурой:

  • ~/.claude/settings.json - пользовательские настройки (для всех проектов)
  • .claude/settings.json - настройки проекта (общие для команды, попадают в commit)
  • .claude/settings.local.json - локальные настройки проекта (не попадают в commit)
  • Managed policy - настройки уровня организации
  • hooks/hooks.json плагина - hooks в области плагина
  • Frontmatter Skill/Agent - hooks на время жизни компонента

Базовая структура конфигурации

json
{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Ключевые поля:

FieldDescriptionExample
matcherPattern to match tool names (case-sensitive)"Write", "Edit|Write", "*"
hooksArray of hook definitions[{ "type": "command", ... }]
typeHook type: "command" (bash), "prompt" (LLM), "http" (webhook), "mcp_tool" (MCP tool invocation, v2.1.118+), or "agent" (subagent)"command"
commandShell command to execute"$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"
timeoutOptional timeout in seconds (default 60)30
onceIf true, run the hook only once per sessiontrue

Шаблоны matcher

PatternDescriptionExample
Exact stringMatches specific tool"Write"
Regex patternMatches multiple tools"Edit|Write"
Comma-separatedMatches any listed tool (v2.1.191+)"Write,Edit"
WildcardMatches all tools"*" or ""
MCP toolsServer and tool pattern"mcp__memory__.*"

Матчеры сопоставляются точно (v2.1.195+). Идентификатор с дефисом (например, имя MCP-инструмента, содержащее дефис) больше не приводит к случайному совпадению как подстроки с другим инструментом. Матчеры со списком через запятую, например "Write,Edit", срабатывают для любого инструмента из списка - в более ранних сборках они молча не срабатывали никогда.

Значения матчера InstructionsLoaded:

Matcher ValueDescription
session_startInstructions loaded at session startup
nested_traversalInstructions loaded during nested directory traversal
path_glob_matchInstructions loaded via path glob pattern matching

Сужение через условия if (пути в аргументах инструмента)

Поле matcher выбирает hook по имени инструмента ("Write", "Edit|Write", "*"). Чтобы отфильтровать точнее - по аргументам инструмента, например запускать hook только когда правка затрагивает src/, или блокировать чтение секретных файлов, - добавьте условие if к конкретному обработчику hook. Это не то же самое, что matcher по имени инструмента: matcher определяет, какой инструмент, а if - какой именно вызов.

if использует синтаксис правил разрешений (ToolName(pattern)) и проверяется по имени инструмента вместе с его аргументами. Для Read/Edit/Write шаблон пути подчиняется семантике gitignore с теми же якорями, что и в правилах разрешений: простое имя вроде .env совпадает на любой глубине, src/** отсчитывается от текущего каталога, /src/** - от корня проекта, ~/... - от домашнего каталога, а //... - это абсолютный путь в файловой системе.

Поле if располагается на уровне обработчика hook - рядом с type и command внутри массива hooks, - а не на уровне matcher:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(src/**)",
            "command": "./hooks/lint-src.sh"
          }
        ]
      },
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "if": "Read(.env)",
            "command": "./hooks/block-secret-read.sh"
          }
        ]
      }
    ]
  }
}

Примеры допустимых шаблонов if: Edit(src/**) (правки внутри src/), Read(~/.ssh/**) (чтение любого SSH-ключа), Read(.env) (любой .env в текущей директории или ниже по дереву), Bash(git push *) (только подкоманды git push).

Обновление v2.1.214: односегментный шаблон dir/** в условии if у hook (например, Edit(src/**)) теперь совпадает только с <cwd>/dir - а не с одноимённой директорией на любой глубине дерева. Раньше src/** совпадал в том числе с foo/src/**. Если нужно совпадение на любой глубине, используйте **/dir/**. Важно: это сужение действует только для условий if: в hook и для автоодобрения по allow-правилам - правила запрета (deny) и запроса (ask) по-прежнему сопоставляются с dir/** на любой глубине.

Типы hook

Claude Code поддерживает пять типов hook:

Command Hooks

Тип hook по умолчанию. Выполняет shell-команду и обменивается данными через JSON по stdin/stdout, а также через коды возврата.

json
{
  "type": "command",
  "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
  "timeout": 60
}

Exec-форма (args)

Добавлено в v2.1.139.

Вместо shell-формы "command": "..." command hook может запускать бинарник напрямую через execve(), передавая массив args. Shell-парсинг при этом не выполняется, поэтому подстановки путей никогда не требуют экранирования кавычками, а конфигурация защищена от ошибок, связанных с shell-инъекциями.

json
{
  "type": "command",
  "args": ["python3", "$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py", "--strict"],
  "timeout": 60
}

Эти две формы взаимоисключающие - hook, в котором одновременно заданы command и args, отклоняется при загрузке конфигурации. Используйте command, когда нужны конвейеры, перенаправления, объединение команд через &amp;&amp; или подстановки shell; используйте args, когда вызываете один бинарник с аргументами.

HTTP Hooks

> Добавлено в v2.1.63.

Удалённые webhook-эндпоинты, которые получают тот же JSON на вход, что и command hooks. HTTP hooks отправляют JSON методом POST на указанный URL и получают JSON-ответ. Когда sandboxing включён, запросы HTTP hooks маршрутизируются через sandbox. Для интерполяции переменных окружения в URL необходимо в целях безопасности явно задать список allowedEnvVars.

json
{
  "hooks": {
    "PostToolUse": [{
      "type": "http",
      "url": "https://my-webhook.example.com/hook",
      "matcher": "Write"
    }]
  }
}

Ключевые свойства:

  • "type": "http" - обозначает hook как HTTP
  • "url" - URL endpoint webhook'а
  • Маршрутизируется через sandbox, если sandbox включён
  • Требует явно заданного списка allowedEnvVars для подстановки любых переменных окружения в URL

Prompt Hooks

Промпты, вычисляемые LLM: содержимое hook'а - это промпт, который обрабатывает Claude. Применяются преимущественно с событиями Stop и SubagentStop для интеллектуальной проверки завершения задачи.

json
{
  "type": "prompt",
  "prompt": "Evaluate if Claude completed all requested tasks.",
  "timeout": 30
}

LLM анализирует prompt и возвращает структурированное решение (подробнее см. Prompt-Based Hooks).

MCP Tool Hooks

Добавлено в v2.1.118.

Тип mcp_tool напрямую вызывает настроенный MCP-инструмент; в конфигурации указываются MCP-сервер и имя инструмента, а не shell-команда или URL. Это удобно, когда логика валидации или реакции уже реализована в одном из настроенных вами MCP-серверов.

json
{
  "matcher": "Edit",
  "hooks": [{
    "type": "mcp_tool",
    "server": "my-mcp-server",
    "tool": "validate_edit"
  }]
}

Ключевые свойства:

  • "type": "mcp_tool" - определяет этот hook как вызов MCP-инструмента
  • "server" - имя настроенного MCP-сервера
  • "tool" - имя вызываемого инструмента на этом сервере

Входные данные hook (имя инструмента, его входные параметры, контекст сессии) передаются как аргументы MCP-инструмента. О настройке MCP-серверов см. MCP server setup.

Agent Hooks

Проверочные hooks на основе субагентов: для оценки условий или выполнения сложных проверок запускается отдельный агент. В отличие от prompt hooks (одношаговая оценка через LLM), agent hooks могут использовать инструменты и выполнять многошаговые рассуждения.

json
{
  "type": "agent",
  "prompt": "Verify the code changes follow our architecture guidelines. Check the relevant design docs and compare.",
  "timeout": 120
}

Ключевые свойства:

  • "type": "agent" - обозначает, что это agent hook
  • "prompt" - описание задачи для субагента
  • Агент может использовать инструменты (Read, Grep, Bash и т. д.) для выполнения проверки
  • Возвращает структурированное решение, аналогичное prompt hooks

События hook

Claude Code поддерживает 31 событие hook:

EventWhen TriggeredMatcher InputCan BlockCommon Use
SessionStartSession begins/resumes/clear/compactstartup/resume/clear/compact/forkNoEnvironment setup
SetupInitial environment setup (one-time per session)(none)NoProvision tooling, install deps
InstructionsLoadedAfter CLAUDE.md or rules file loaded(none)NoModify/filter instructions
UserPromptSubmitUser submits prompt(none)YesValidate prompts
UserPromptExpansionUser prompt is expanded (e.g., @ mentions, slash commands resolved)(none)YesTransform or inspect expanded prompt
PreToolUseBefore tool executionTool nameYes (allow/deny/ask)Validate, modify inputs
PermissionRequestPermission dialog shownTool nameYesAuto-approve/deny
PermissionDeniedUser denies a permission promptTool nameNoLogging, analytics, policy enforcement
PostToolUseAfter tool succeedsTool nameNoAdd context, feedback
PostToolUseFailureTool execution failsTool nameNoError handling, logging
PostToolBatchAfter a batch of tool uses completes(none)NoAggregate reporting, batched validation
NotificationNotification sentNotification typeNoCustom notifications
MessageDisplayWhile assistant message text is displayed(none)NoTransform or hide displayed message text (v2.1.152)
SubagentStartSubagent spawnedAgent type nameNoSubagent setup
SubagentStopSubagent finishesAgent type nameYesSubagent validation
StopClaude finishes responding(none)YesTask completion check
StopFailureAPI error ends turn(none)NoError recovery, logging
TeammateIdleAgent team teammate idle(none)YesTeammate coordination
TaskCompletedTask marked complete(none)YesPost-task actions
TaskCreatedTask created via TaskCreate(none)NoTask tracking, logging
ConfigChangeConfig file changes(none)Yes (except policy)React to config updates
CwdChangedWorking directory changes(none)NoDirectory-specific setup
DirectoryAddedNew working directory registered mid-session via /add-dir or the SDK register_repo_root control request (v2.1.219)(none)NoSet up tooling for a newly added directory
FileChangedWatched file changes(none)NoFile monitoring, rebuild
PreCompactBefore context compactionmanual/autoNoPre-compact actions
PostCompactAfter compaction completes(none)NoPost-compact actions
WorktreeCreateWorktree being created(none)Yes (path return)Worktree initialization
WorktreeRemoveWorktree being removed(none)NoWorktree cleanup
ElicitationMCP server requests user input(none)YesInput validation
ElicitationResultUser responds to elicitation(none)YesResponse processing
SessionEndSession terminates(none)NoCleanup, final logging

Длительность в PostToolUse (v2.1.119): входные данные hook'ов PostToolUse и PostToolUseFailure теперь включают duration_ms - подробнее см. в разделе PostToolUse.

PreToolUse

Запускается после того, как Claude сформировал параметры инструмента, но до их обработки. Используйте этот hook для валидации или изменения входных параметров инструмента.

Конфигурация:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
          }
        ]
      }
    ]
  }
}

Часто используемые матчеры: Task, Bash, Glob, Grep, Read, Edit, Write, WebFetch, WebSearch

Управление выводом:

  • permissionDecision: "allow", "deny", "ask" или "defer"
    • "allow" пропускает запрос разрешения (кроме инструментов, требующих взаимодействия с пользователем, и инструментов-коннекторов, для которых ваша организация установила значение ask)
    • "deny" блокирует вызов инструмента
    • "ask" запрашивает подтверждение у пользователя
    • "defer" корректно завершает работу, чтобы инструмент можно было возобновить позже; при этом значении поля permissionDecisionReason, updatedInput и additionalContext игнорируются
    • Правила deny и ask всё равно применяются независимо от того, что вернул hook. Если несколько hook-ов PreToolUse дают противоречивые решения, приоритет следующий: deny > defer > ask > allow
  • permissionDecisionReason: обоснование решения. Для "allow" и "ask" показывается пользователю (но не Claude); для "deny" показывается Claude; для "defer" игнорируется
  • updatedInput: изменённые входные параметры инструмента

PostToolUse

Запускается сразу после завершения работы инструмента. Используется для проверки результатов, логирования или передачи дополнительного контекста обратно Claude.

Конфигурация:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py"
          }
        ]
      }
    ]
  }
}

Управление выводом:

  • решение "block" передаёт Claude обратную связь
  • additionalContext: контекст, добавляемый для Claude

Дополнительные поля ввода (v2.1.119):

FieldTypeDescription
duration_msnumberTool execution time in milliseconds. Excludes time spent in permission prompts and PreToolUse hook execution. Available on both PostToolUse and PostToolUseFailure hooks.

Восстановимая блокировка (continueOnBlock, v2.1.139)

По умолчанию hook PostToolUse, возвращающий "decision": "block", прерывает текущий ход. Укажите у hook "continueOnBlock": true, чтобы вместо прерывания отказ возвращался Claude как tool_result - тогда модель сможет учесть этот отклик и повторить вызов или скорректировать действия.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/policy-check.py",
            "continueOnBlock": true
          }
        ]
      }
    ]
  }
}

Используйте это, когда reason у hook - это то, на что Claude может отреагировать (например, «этот файл доступен только для чтения; запиши в другое место»); не указывайте это, когда блокировка должна полностью прервать ход.

UserPromptSubmit

Срабатывает, когда пользователь отправляет prompt, до того как Claude начнёт его обрабатывать.

Конфигурация:

json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py"
          }
        ]
      }
    ]
  }
}

Управление выводом:

  • decision: "block" - прервать обработку
  • reason: причина блокировки
  • additionalContext: контекст, добавляемый к prompt

Stop и SubagentStop

Срабатывают, когда Claude завершает ответ (Stop) или когда завершает работу субагент (SubagentStop). Поддерживают проверку на основе prompt для интеллектуального определения факта завершения задачи.

Дополнительное поле ввода: hook'и Stop и SubagentStop получают в JSON-входе поле last_assistant_message, содержащее последнее сообщение Claude или субагента перед остановкой. Это удобно для оценки того, была ли задача выполнена.

Конфигурация:

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude completed all requested tasks.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

> Защитный лимит на последовательные блокировки (v2.1.143): Если Stop hook возвращает &quot;decision&quot;: &quot;block&quot; (или устанавливает continue: false) 8 раз подряд в рамках одного хода, Claude Code принудительно прерывает цикл и завершает сессию с предупреждением. Порог можно переопределить через переменную окружения CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=&lt;integer&gt; (значение 0 полностью отключает лимит). Это защищает от ситуации, когда сбойный Stop hook бесконечно зацикливает сессию.

Поле возврата (v2.1.163): Stop или SubagentStop hook может вернуть hookSpecificOutput.additionalContext, чтобы передать Claude обратную связь и продолжить ход, не показывая метку ошибки. Раньше повлиять на модель из Stop hook было неудобно; теперь hook может аккуратно внедрить контекст, избегая появления метки ошибки, характерной для прежних способов передачи обратной связи (например, &quot;decision&quot;: &quot;block&quot;).

json
{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Reminder: run the test suite before declaring done."
  }
}

SubagentStart

Срабатывает в момент запуска субагента. В matcher передаётся имя типа агента, что позволяет применять hooks к конкретным типам субагентов.

Конфигурация:

json
{
  "hooks": {
    "SubagentStart": [
      {
        "matcher": "code-review",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-init.sh"
          }
        ]
      }
    ]
  }
}

SessionStart

Запускается при старте или возобновлении сессии. Может сохранять переменные окружения.

Matchers: startup, resume, clear, compact, fork

Обновление v2.1.214: форкнутая сессия теперь возвращает источник "fork" - ранее возвращался "resume".

Особенность: используйте CLAUDE_ENV_FILE для сохранения переменных окружения (также доступно в hooks CwdChanged и FileChanged):

bash
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi
exit 0

Вывод на уровне сессии (v2.1.152): hook SessionStart может вернуть JSON для повторного сканирования skills и установки заголовка сессии:

json
{
  "reloadSkills": true,
  "hookSpecificOutput": {
    "sessionTitle": "Payments migration"
  }
}

Верхнеуровневый reloadSkills: true запускает повторное сканирование skills в текущей сессии (то же действие, что и команда /reload-skills), благодаря чему skills, только что установленные hook'ом, становятся доступны немедленно. hookSpecificOutput.sessionTitle задаёт отображаемый заголовок сессии при её запуске и возобновлении.

SessionEnd

Выполняется при завершении сессии - для очистки ресурсов или финальной записи в лог. Не может предотвратить завершение.

Значения поля Reason:

  • clear - пользователь очистил сессию
  • logout - пользователь вышел из аккаунта
  • prompt_input_exit - пользователь вышел через поле ввода промпта
  • other - иная причина

Конфигурация:

json
{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\""
          }
        ]
      }
    ]
  }
}

Событие уведомления

Обновлённые matchers для событий уведомлений:

  • permission_prompt - уведомление с запросом разрешения
  • idle_prompt - уведомление о простое
  • auth_success - успешная аутентификация
  • elicitation_dialog - диалог, отображаемый пользователю
  • agent_needs_input - фоновому agent требуется ввод (v2.1.198)
  • agent_completed - фоновый agent завершил работу (v2.1.198)

Hooks на уровне компонента

Hooks можно привязывать к конкретным компонентам (skills, agents, commands) через их frontmatter:

В SKILL.md, agent.md или command.md:

yaml
---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/check.sh"
          once: true  # Only run once per session
---

Поддерживаемые события для hooks в компонентах: PreToolUse, PostToolUse, Stop

Это позволяет определять hooks прямо в том компоненте, который их использует, благодаря чему связанный код хранится вместе.

Hooks во frontmatter субагента

Если во frontmatter субагента задан hook Stop, он автоматически преобразуется в hook SubagentStop, привязанный к этому субагенту. Это гарантирует, что stop hook срабатывает именно при завершении данного субагента, а не при остановке основной сессии.

yaml
---
name: code-review-agent
description: Automated code review subagent
hooks:
  Stop:
    - hooks:
        - type: prompt
          prompt: "Verify the code review is thorough and complete."
  # The above Stop hook auto-converts to SubagentStop for this subagent
---

Требуется доверие к рабочей области (v2.1.218): hooks во frontmatter project-субагента теперь требуют подтверждения доверия к рабочей области для папки, из которой был загружен файл агента, - иначе они не запустятся. До версии v2.1.218 такие hooks могли выполняться из папок, которым вы не давали доверие. О том, для каких scopes сделано исключение, см. документацию по субагентам.

Событие PermissionRequest

Обрабатывает запросы разрешений с пользовательским форматом вывода:

json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow|deny",
      "updatedInput": {},
      "message": "Custom message",
      "interrupt": false
    }
  }
}

Входные и выходные данные hook

JSON на входе (через stdin)

Все hooks получают JSON на вход через stdin:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/directory",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.js",
    "content": "..."
  },
  "tool_use_id": "toolu_01ABC123...",
  "agent_id": "agent-abc123",
  "agent_type": "main",
  "worktree": "/path/to/worktree",
  "effort": { "level": "medium" }
}

Общие поля:

FieldDescription
session_idUnique session identifier
transcript_pathPath to the conversation transcript file
cwdCurrent working directory
prompt_idUUID of the prompt being processed; correlates with the OpenTelemetry prompt.id attribute (v2.1.196)
hook_event_nameName of the event that triggered the hook
agent_idIdentifier of the agent running this hook
agent_typeType of agent ("main", subagent type name, etc.)
worktreePath to the git worktree, if the agent is running in one
effort.level(v2.1.133+) Active effort level: low, medium, high, xhigh, or max

Коды выхода

Exit CodeMeaningBehavior
0SuccessContinue, parse JSON stdout
2Blocking errorBlock operation, stderr shown as error
OtherNon-blocking errorContinue, stderr shown in verbose mode

JSON-вывод (stdout, код возврата 0)

json
{
  "continue": true,
  "stopReason": "Optional message if stopping",
  "suppressOutput": false,
  "systemMessage": "Optional warning message",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "File is in allowed directory",
    "updatedInput": {
      "file_path": "/modified/path.js"
    }
  }
}

Область применения (v2.1.121+): hookSpecificOutput.updatedToolOutput теперь учитывается для всех инструментов, а не только для MCP-инструментов. Hook PostToolUse, навешенный на Bash, Edit, Read и т. д., может переписать вывод инструмента до того, как его увидит Claude, - это удобно для маскирования секретов, нормализации diff-ов или отсеивания шумного вывода команд. Пример (удаление ANSI-кодов цвета из вывода Bash):

json
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "updatedToolOutput": "<plain-text output with ANSI escapes removed>"
  }
}

terminalSequence (v2.1.141)

Hook-и могут выдавать сырые OSC-последовательности (operating system command) - для этого в JSON-выводе задаётся поле terminalSequence. Когда hook возвращает результат, хост записывает эту последовательность в свой управляющий терминал. Это удобно для десктопных уведомлений, обновления заголовка окна и подачи звукового сигнала терминала (bell) - без необходимости держать собственный TTY.

FieldTypeDescription
terminalSequencestringRaw escape sequence (typically OSC 9 / OSC 0 / OSC 777). Written to the host terminal verbatim.
Пример - отправить desktop-уведомление через OSC 9 по завершении длительной задачи:
json
{
  "terminalSequence": "]9;Task complete"
}

Настройте это в hook Stop, чтобы уведомление появлялось, когда Claude завершает ответ. Поддержка escape-последовательностей зависит от терминала; Kitty/iTerm2/Windows Terminal поддерживают OSC 9.

Переменные окружения

VariableAvailabilityDescription
CLAUDE_PROJECT_DIRAll hooksAbsolute path to project root
CLAUDE_ENV_FILESessionStart, CwdChanged, FileChangedFile path for persisting env vars
CLAUDE_CODE_REMOTEAll hooks"true" if running in remote environments
${CLAUDE_PLUGIN_ROOT}Plugin hooksPath to plugin directory
${CLAUDE_PLUGIN_DATA}Plugin hooksPath to plugin data directory
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MSSessionEnd hooksConfigurable timeout in milliseconds for SessionEnd hooks (overrides default)
CLAUDE_CODE_SESSION_IDBash tool subprocesses (v2.1.132+)Session UUID; matches the session_id field in hook input JSON. Use to correlate bash logs with hook telemetry.
CLAUDE_EFFORTBash tool subprocesses (v2.1.133+)Active effort level (low/medium/high/xhigh/max); matches effort.level in hook input JSON.
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPProcess-wide (v2.1.143+)Max consecutive Stop-hook blocks before the session ends with a warning (default 8). Set to 0 to disable the cap.

Hooks на основе промптов

Для событий Stop и SubagentStop можно использовать проверку с помощью LLM:

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Review if all tasks are complete. Return your decision.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Текущая дата: вторник, 4 августа 2026 г.

<query>

Схема ответа LLM: </query>

json
{
  "decision": "approve",
  "reason": "All tasks completed successfully",
  "continue": false,
  "stopReason": "Task complete"
}

Примеры

Пример 1: Валидатор Bash-команд (PreToolUse)

Файл: .claude/hooks/validate-bash.py

python
#!/usr/bin/env python3
import json
import sys
import re

BLOCKED_PATTERNS = [
    (r"\brm\s+-rf\s+/", "Blocking dangerous rm -rf / command"),
    (r"\bsudo\s+rm", "Blocking sudo rm command"),
]

def main():
    input_data = json.load(sys.stdin)

    tool_name = input_data.get("tool_name", "")
    if tool_name != "Bash":
        sys.exit(0)

    command = input_data.get("tool_input", {}).get("command", "")

    for pattern, message in BLOCKED_PATTERNS:
        if re.search(pattern, command):
            print(message, file=sys.stderr)
            sys.exit(2)  # Exit 2 = blocking error

    sys.exit(0)

if __name__ == "__main__":
    main()

Текущая дата: вторник, 4 августа 2026 г.

<query>

Конфигурация: </query>

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\""
          }
        ]
      }
    ]
  }
}

Пример 2: сканер безопасности (PostToolUse)

Файл: .claude/hooks/security-scan.py

python
#!/usr/bin/env python3
import json
import sys
import re

SECRET_PATTERNS = [
    (r"password\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded password"),
    (r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded API key"),
]

def main():
    input_data = json.load(sys.stdin)

    tool_name = input_data.get("tool_name", "")
    if tool_name not in ["Write", "Edit"]:
        sys.exit(0)

    tool_input = input_data.get("tool_input", {})
    content = tool_input.get("content", "") or tool_input.get("new_string", "")
    file_path = tool_input.get("file_path", "")

    warnings = []
    for pattern, message in SECRET_PATTERNS:
        if re.search(pattern, content, re.IGNORECASE):
            warnings.append(message)

    if warnings:
        output = {
            "hookSpecificOutput": {
                "hookEventName": "PostToolUse",
                "additionalContext": f"Security warnings for {file_path}: " + "; ".join(warnings)
            }
        }
        print(json.dumps(output))

    sys.exit(0)

if __name__ == "__main__":
    main()

Пример 3. Автоформатирование кода (PostToolUse)

Файл: .claude/hooks/format-code.sh

bash
#!/bin/bash

# Read JSON from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")

if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
    exit 0
fi

# Format based on file extension
case "$FILE_PATH" in
    *.js|*.jsx|*.ts|*.tsx|*.json)
        command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null
        ;;
    *.py)
        command -v black &>/dev/null && black "$FILE_PATH" 2>/dev/null
        ;;
    *.go)
        command -v gofmt &>/dev/null && gofmt -w "$FILE_PATH" 2>/dev/null
        ;;
esac

exit 0

Пример 4: валидатор промпта (UserPromptSubmit)

Файл: .claude/hooks/validate-prompt.py

python
#!/usr/bin/env python3
import json
import sys
import re

BLOCKED_PATTERNS = [
    (r"delete\s+(all\s+)?database", "Dangerous: database deletion"),
    (r"rm\s+-rf\s+/", "Dangerous: root deletion"),
]

def main():
    input_data = json.load(sys.stdin)
    prompt = input_data.get("user_prompt", "") or input_data.get("prompt", "")

    for pattern, message in BLOCKED_PATTERNS:
        if re.search(pattern, prompt, re.IGNORECASE):
            output = {
                "decision": "block",
                "reason": f"Blocked: {message}"
            }
            print(json.dumps(output))
            sys.exit(0)

    sys.exit(0)

if __name__ == "__main__":
    main()

Пример 5: Интеллектуальный Stop Hook (на основе промпта)

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Review if Claude completed all requested tasks. Check: 1) Were all files created/modified? 2) Were there unresolved errors? If incomplete, explain what's missing.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Пример 6: Трекер использования контекста (пары hook'ов)

Отслеживайте расход токенов на каждый запрос с помощью связки hook'ов UserPromptSubmit (перед сообщением) и Stop (после ответа).

Файл: .claude/hooks/context-tracker.py

python
#!/usr/bin/env python3
"""
Context Usage Tracker - Tracks token consumption per request.

Uses UserPromptSubmit as "pre-message" hook and Stop as "post-response" hook
to calculate the delta in token usage for each request.

Token Counting Methods:
1. Character estimation (default): ~4 chars per token, no dependencies
2. tiktoken (optional): More accurate (~90-95%), requires: pip install tiktoken
"""
import json
import os
import sys
import tempfile

# Configuration
CONTEXT_LIMIT = 128000  # Claude's context window (adjust for your model)
USE_TIKTOKEN = False    # Set True if tiktoken is installed for better accuracy


def get_state_file(session_id: str) -> str:
    """Get temp file path for storing pre-message token count, isolated by session."""
    return os.path.join(tempfile.gettempdir(), f"claude-context-{session_id}.json")


def count_tokens(text: str) -> int:
    """
    Count tokens in text.

    Uses tiktoken with p50k_base encoding if available (~90-95% accuracy),
    otherwise falls back to character estimation (~80-90% accuracy).
    """
    if USE_TIKTOKEN:
        try:
            import tiktoken
            enc = tiktoken.get_encoding("p50k_base")
            return len(enc.encode(text))
        except ImportError:
            pass  # Fall back to estimation

    # Character-based estimation: ~4 characters per token for English
    return len(text) // 4


def read_transcript(transcript_path: str) -> str:
    """Read and concatenate all content from transcript file."""
    if not transcript_path or not os.path.exists(transcript_path):
        return ""

    content = []
    with open(transcript_path, "r") as f:
        for line in f:
            try:
                entry = json.loads(line.strip())
                # Extract text content from various message formats
                if "message" in entry:
                    msg = entry["message"]
                    if isinstance(msg.get("content"), str):
                        content.append(msg["content"])
                    elif isinstance(msg.get("content"), list):
                        for block in msg["content"]:
                            if isinstance(block, dict) and block.get("type") == "text":
                                content.append(block.get("text", ""))
            except json.JSONDecodeError:
                continue

    return "\n".join(content)


def handle_user_prompt_submit(data: dict) -> None:
    """Pre-message hook: Save current token count before request."""
    session_id = data.get("session_id", "unknown")
    transcript_path = data.get("transcript_path", "")

    transcript_content = read_transcript(transcript_path)
    current_tokens = count_tokens(transcript_content)

    # Save to temp file for later comparison
    state_file = get_state_file(session_id)
    with open(state_file, "w") as f:
        json.dump({"pre_tokens": current_tokens}, f)


def handle_stop(data: dict) -> None:
    """Post-response hook: Calculate and report token delta."""
    session_id = data.get("session_id", "unknown")
    transcript_path = data.get("transcript_path", "")

    transcript_content = read_transcript(transcript_path)
    current_tokens = count_tokens(transcript_content)

    # Load pre-message count
    state_file = get_state_file(session_id)
    pre_tokens = 0
    if os.path.exists(state_file):
        try:
            with open(state_file, "r") as f:
                state = json.load(f)
                pre_tokens = state.get("pre_tokens", 0)
        except (json.JSONDecodeError, IOError):
            pass

    # Calculate delta
    delta_tokens = current_tokens - pre_tokens
    remaining = CONTEXT_LIMIT - current_tokens
    percentage = (current_tokens / CONTEXT_LIMIT) * 100

    # Report usage
    method = "tiktoken" if USE_TIKTOKEN else "estimated"
    print(f"Context ({method}): ~{current_tokens:,} tokens ({percentage:.1f}% used, ~{remaining:,} remaining)", file=sys.stderr)
    if delta_tokens > 0:
        print(f"This request: ~{delta_tokens:,} tokens", file=sys.stderr)


def main():
    data = json.load(sys.stdin)
    event = data.get("hook_event_name", "")

    if event == "UserPromptSubmit":
        handle_user_prompt_submit(data)
    elif event == "Stop":
        handle_stop(data)

    sys.exit(0)


if __name__ == "__main__":
    main()

Текущая дата: вторник, 4 августа 2026 г.

<query>

Конфигурация: </query>

json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
          }
        ]
      }
    ]
  }
}

Как это работает:

  1. UserPromptSubmit срабатывает до обработки вашего промпта - сохраняет текущее число токенов
  2. Stop срабатывает после ответа Claude - вычисляет разницу и выводит статистику использования
  3. Сессии изолируются друг от друга через session_id в имени временного файла

Методы подсчёта токенов:

MethodAccuracyDependenciesSpeed
Character estimation~80-90%None<1ms
tiktoken (p50k_base)~90-95%pip install tiktoken<10ms

Примечание: Anthropic не выпускала официального офлайн-токенизатора. Оба метода дают лишь приблизительный результат. В транскрипт попадают запросы пользователя, ответы Claude и вывод инструментов, но НЕ системные промпты и не внутренний контекст.

Пример 7: предзаполнение разрешений для auto-mode (одноразовый скрипт настройки)

Одноразовый скрипт настройки, который добавляет в ~/.claude/settings.json около 67 безопасных правил разрешений, соответствующих базовому набору auto-mode в Claude Code - без каких-либо hook'ов и без запоминания будущих решений. Запустите его один раз; повторный запуск безопасен (уже существующие правила пропускаются).

Файл: 09-advanced-features/setup-auto-mode-permissions.py

bash
# Preview what would be added
python3 09-advanced-features/setup-auto-mode-permissions.py --dry-run

# Apply
python3 09-advanced-features/setup-auto-mode-permissions.py

Что добавляется:

CategoryExamples
Built-in toolsRead(*), Edit(*), Write(*), Glob(*), Grep(*), Agent(*), WebSearch(*)
Git readBash(git status:*), Bash(git log:*), Bash(git diff:*)
Git write (local)Bash(git add:*), Bash(git commit:*), Bash(git checkout:*)
Package managersBash(npm install:*), Bash(pip install:*), Bash(cargo build:*)
Build & testBash(make:*), Bash(pytest:*), Bash(go test:*)
Common shellBash(ls:*), Bash(cat:*), Bash(find:*), Bash(cp:*), Bash(mv:*)
GitHub CLIBash(gh pr view:*), Bash(gh pr create:*), Bash(gh issue list:*)
Что намеренно исключено (никогда не добавляется этим скриптом):
  • rm -rf, sudo, force push, git reset --hard
  • DROP TABLE, kubectl delete, terraform destroy
  • npm publish, curl | bash, деплой в production

Пример 8: логирование прогресса обучения (SessionEnd)

Записывайте, какие модули вы изучили, в конце каждой сессии Claude Code. Прогресс хранится в ~/.claude-howto-progress.json - вне репозитория, поэтому он переживает git pull без перезаписи.

Почему SessionEnd, а не Stop? Stop срабатывает после каждого ответа Claude. SessionEnd срабатывает один раз - при завершении сессии, что как раз и нужно для итоговой записи в дневник.

Почему /dev/tty для ввода? Hook-скрипты получают JSON-payload хука через stdin, поэтому интерактивный read должен обращаться напрямую к /dev/tty, чтобы достучаться до терминала.

Файл: 06-hooks/session-end.sh

bash
#!/usr/bin/env bash
# SessionEnd hook: prompts for modules worked on, then appends a session record
# to ~/.claude-howto-progress.json for persistent learning progress tracking.

PROGRESS_FILE="$HOME/.claude-howto-progress.json"

# Guard: only run inside this repo
if [[ "$CLAUDE_PROJECT_DIR" != *"claude-howto"* ]] && [[ "$PWD" != *"claude-howto"* ]]; then
  exit 0
fi

if [ ! -f "$PROGRESS_FILE" ]; then
  echo '{"sessions":[]}' > "$PROGRESS_FILE"
fi

DATE=$(date +"%Y-%m-%d")
TIME=$(date +"%H:%M")

echo ""
echo " Which modules did you work on? (e.g. 06,07 or press Enter to skip)"
echo " 01=Slash  02=Memory  03=Skills  04=Subagents  05=MCP"
echo " 06=Hooks  07=Plugins 08=Checkpoints 09=Advanced 10=CLI"
printf " > "
read -r INPUT </dev/tty

if [ -z "$INPUT" ] || [ "$INPUT" = "skip" ]; then
  exit 0
fi

MODULES_JSON=$(echo "$INPUT" | tr ',' '\n' | tr -d ' ' | while read -r m; do
  case "$m" in
    01) echo '"01-slash-commands"' ;;
    02) echo '"02-memory"' ;;
    03) echo '"03-skills"' ;;
    04) echo '"04-subagents"' ;;
    05) echo '"05-mcp"' ;;
    06) echo '"06-hooks"' ;;
    07) echo '"07-plugins"' ;;
    08) echo '"08-checkpoints"' ;;
    09) echo '"09-advanced-features"' ;;
    10) echo '"10-cli"' ;;
    *)  echo "\"$m\"" ;;
  esac
done | paste -sd ',' -)

printf " Notes? (optional, press Enter to skip): "
read -r NOTES </dev/tty

# Pass NOTES as a separate argument so Python handles JSON escaping -
# avoids broken JSON when notes contain quotes or backslashes.
python3 - "$PROGRESS_FILE" "$DATE" "$TIME" "$MODULES_JSON" "$NOTES" <<'PYEOF'
import sys, json

path, date, time_str, modules_raw, notes = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]

new_session = {
    "date": date,
    "time": time_str,
    "modules": json.loads(f"[{modules_raw}]") if modules_raw else [],
    "notes": notes,
}

with open(path, 'r') as f:
    data = json.load(f)

data.setdefault('sessions', []).append(new_session)

with open(path, 'w') as f:
    json.dump(data, f, indent=2)
PYEOF

echo " Saved to $PROGRESS_FILE"

Установка - скопируйте скрипт в директорию hook'ов проекта, чтобы путь в settings.json корректно разрешался:

bash
mkdir -p .claude/hooks
cp 06-hooks/session-end.sh .claude/hooks/
chmod +x .claude/hooks/session-end.sh

Конфигурация.claude/settings.json):

json
{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-end.sh\""
          }
        ]
      }
    ]
  }
}

Вывод - ~/.claude-howto-progress.json:

json
{
  "sessions": [
    {
      "date": "2026-04-18",
      "time": "14:32",
      "modules": ["06-hooks", "07-plugins"],
      "notes": "Installed first hook, tried pre-commit example"
    }
  ]
}

Ключевые демонстрируемые паттерны:

PatternWhy it matters
SessionEnd eventFires once on exit - not after every response like Stop
read -r INPUT </dev/ttyHooks own stdin (JSON payload); use /dev/tty for user input
$CLAUDE_PROJECT_DIRPortable path - never hardcode /Users/yourname/...
Guard clause at topPrevents the hook running in unrelated projects if installed globally
Store outside the repo~/ path survives git pull without overwriting your data
Дополнение: визуальный трекер прогресса

Чтобы получить полноценный интерфейс с чекбоксами по всем 10 модулям, откройте прилагаемый трекер в браузере:

bash
open local-progress/index.html

Прогресс хранится в браузерном localStorage (в репозиторий на диск ничего не пишется). Нажмите Export, чтобы сохранить снимок состояния в виде JSON, и Import - чтобы восстановить его.

Hooks плагинов

Плагины могут объявлять hooks в файле hooks/hooks.json:

Файл: plugins/hooks/hooks.json

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
          }
        ]
      }
    ]
  }
}

Переменные окружения в hooks плагинов:

  • ${CLAUDE_PLUGIN_ROOT} - путь к директории плагина
  • ${CLAUDE_PLUGIN_DATA} - путь к директории данных плагина

Это позволяет плагинам подключать собственные hooks для валидации и автоматизации.

Hooks для инструментов MCP

Инструменты MCP соответствуют шаблону mcp__<server>__<tool>:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"systemMessage\": \"Memory operation logged\"}'"
          }
        ]
      }
    ]
  }
}

Вопросы безопасности

Отказ от ответственности

ИСПОЛЬЗУЙТЕ НА СВОЙ СТРАХ И РИСК: hook’и выполняют произвольные shell-команды. Вы несёте полную ответственность за:

  • команды, которые вы настраиваете;
  • права доступа к файлам и их изменения;
  • возможную потерю данных или повреждение системы;
  • тестирование hook’ов в безопасных окружениях перед использованием в production.

Замечания по безопасности

  • Требуется доверие к рабочей области: команды, выводимые hook’ами statusLine и fileSuggestion, теперь вступают в силу только после подтверждения доверия к рабочей области.
  • Размер терминала для status-line (v2.1.153): скрипты команд status-line теперь получают переменные окружения COLUMNS и LINES, что позволяет адаптировать вывод под ширину/высоту терминала (например, [ "$COLUMNS" -lt 80 ] && short_output).
  • HTTP-hook’и и переменные окружения: для интерполяции переменных окружения в URL HTTP-hook’и требуют явного указания списка allowedEnvVars. Это предотвращает случайную утечку чувствительных переменных окружения на удалённые endpoint’ы.
  • Иерархия управляемых настроек: параметр disableAllHooks теперь учитывает иерархию управляемых настроек - это значит, что настройки уровня организации могут принудительно отключать hook’и без возможности переопределения со стороны отдельных пользователей.
  • Автоодобрение PowerShell (v2.1.119): команды инструмента PowerShell теперь могут автоматически одобряться в режиме разрешений - аналогично Bash. Это уравнивает возможности для пользователей Windows, работающих в Claude Code с shell-инструментами на базе PowerShell.
  • Закрыта лазейка с автоодобрением «голых» env-var в Bash (v2.1.145): до версии v2.1.145 Bash-команда вида FOO=bar somecommand (встроенное присваивание переменной вместе с командой, отсутствующей в allowlist) могла быть автоматически одобрена, если в allowlist находилось только само FOO=bar. В v2.1.145 эта лазейка закрыта - такие команды теперь вызывают запрос разрешения. Скрипты, полагавшиеся на неявное разрешение, начнут запрашивать подтверждение; чтобы вернуть автоодобрение, явно добавьте правило разрешения Bash(...), охватывающее команду целиком, а не только присваивание переменной.

Лучшие практики

DoDon't
Validate and sanitize all inputsTrust input data blindly
Quote shell variables: "$VAR"Use unquoted: $VAR
Block path traversal (..)Allow arbitrary paths
Use absolute paths with $CLAUDE_PROJECT_DIRHardcode paths
Skip sensitive files (.env, .git/, keys)Process all files
Test hooks in isolation firstDeploy untested hooks
Use explicit allowedEnvVars for HTTP hooksExpose all env vars to webhooks

Отладка

Включение режима отладки

Запустите Claude с флагом отладки, чтобы получить подробные логи hook'ов:

bash
claude --debug

Подробный режим

Нажмите Ctrl+O в Claude Code, чтобы включить подробный режим и отслеживать ход выполнения hook'ов.

Тестирование hook'ов по отдельности

bash
# Test with sample JSON input
echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python3 .claude/hooks/validate-bash.py

# Check exit code
echo $?

Полный пример конфигурации

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"",
            "timeout": 10
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\"",
            "timeout": 30
          },
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py\"",
            "timeout": 10
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py\""
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-init.sh\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Verify all tasks are complete before stopping.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Детали выполнения hook

AspectBehavior
Timeout60 seconds default, configurable per command
ParallelizationAll matching hooks run in parallel
DeduplicationIdentical hook commands deduplicated
EnvironmentRuns in current directory with Claude Code's environment

Устранение неполадок

Hook не выполняется

  • Убедитесь, что синтаксис JSON-конфигурации корректен
  • Проверьте, что паттерн matcher соответствует имени инструмента
  • Убедитесь, что скрипт существует и имеет права на выполнение: chmod +x script.sh
  • Запустите claude --debug, чтобы увидеть логи выполнения hook
  • Убедитесь, что hook читает JSON из stdin (а не из аргументов команды)

Hook неожиданно блокирует выполнение

  • Протестируйте hook на примере JSON: echo '{"tool_name": "Write", ...}' | ./hook.py
  • Проверьте exit code: 0 - разрешить, 2 - заблокировать
  • Проверьте вывод в stderr (отображается при exit code 2)

Ошибки парсинга JSON

  • Всегда читайте из stdin, а не из аргументов командной строки
  • Используйте полноценный парсер JSON (а не манипуляции со строками)
  • Корректно обрабатывайте отсутствующие поля

Установка

Шаг 1. Создайте директорию для hooks

bash
mkdir -p ~/.claude/hooks

Шаг 2: Скопируйте примеры hooks

bash
cp 06-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh

Шаг 3: Настройка в settings

Отредактируйте ~/.claude/settings.json или .claude/settings.json, добавив конфигурацию hook, показанную выше.

Связанные концепции

  • Checkpoints и Rewind - сохранение и восстановление состояния диалога
  • Slash-команды - создание пользовательских slash-команд
  • Skills - переиспользуемые автономные возможности
  • Subagents - делегированное выполнение задач
  • Plugins - упакованные комплекты расширений
  • Продвинутые возможности - знакомство с продвинутыми возможностями Claude Code

Дополнительные материалы


Последнее обновление: 4 августа 2026 г. Версия Claude Code: 2.1.220 Источники:

Совместимые модели: Claude Fable 5, Claude Opus 5, Claude Sonnet 5, Claude Sonnet 4.6, Claude Opus 4.8, Claude Haiku 4.5

ЛОКАЛЬНАЯ ОТМЕТКА · БЕЗ ПРОВЕРКИ
ПРЕДЫДУЩИЙMCP (Model Context Protocol)
СЛЕДУЮЩИЙClaude Code Plugins