Зачем нужны хуки, если есть CLAUDE.md
CLAUDE.md модель читает и обычно слушается. Обычно. Документация Claude Code прямо пишет, что это контекст, а не принудительная конфигурация, и для запретов советует хук PreToolUse.
Цена ошибки бывает высокой. На Hacker News история «An AI agent deleted our production database» (26.04.2026) набрала 860 очков и больше тысячи комментариев, а в июле 2025 агент Replit стёр базу Джейсона Лемкина. Строчка «никогда не трогай прод» в инструкциях от такого не страхует. Хук, который возвращает код 2 на DROP или rm -rf, страхует.
Вторая частая задача: прогнать форматтер или линтер после каждой правки, не надеясь, что агент вспомнит.
События: когда срабатывают хуки
Событий 33. Чаще всего нужны эти:
СобытиеКогдаДля чегоPreToolUseдо вызова инструментазапреты, проверка аргументовPostToolUseпосле успешного вызоваформаттер, линтер, тестыUserPromptSubmitдо обработки вашего сообщениядобавить контекст, отсечь лишнееSessionStart / SessionEndначало и конец сессииподгрузить состояние, убрать за собойStop / SubagentStopагент или субагент закончил ответуведомление, финальная проверкаPreCompactперед сжатием контекстасохранить важноеInstructionsLoadedзагружены файлы инструкцийаудит того, что попало в контекстОстальные (права, задачи, worktree, смена модели и другие) перечислены в справочнике хуков.
Пример: запретить rm -rf через PreToolUse
В .claude/settings.json проекта:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-rm.sh", "timeout": 30 }
]
}
]
}
}Сам скрипт .claude/hooks/no-rm.sh:
#!/bin/sh cmd=$(jq -r '.tool_input.command') case "$cmd" in *"rm -rf"*) echo "Blocked: rm -rf запрещён в этом проекте" >&2; exit 2 ;; esac exit 0
Это набросок, проверьте его у себя. Механика по документации: команда получает JSON события на stdin (tool_name, tool_input и общие поля вроде cwd). Код выхода 2 блокирует вызов, а stderr уходит Claude как причина. Код 0 значит «решения нет», и действие идёт дальше. Stderr при коде 0 Claude не видит.
Matcher из букв, цифр, | и запятых сравнивается точно: Edit|Write ловит ровно эти два инструмента. Любой другой символ превращает его в регулярное выражение, например mcp__memory__.*.
Где лежат хуки и как они сливаются
~/.claude/settings.jsonдля всех проектов;.claude/settings.jsonдля проекта, коммитится;.claude/settings.local.jsonдля себя, в git не идёт;- managed-настройки организации;
hooks/hooks.jsonвнутри плагина;- frontmatter скилла или субагента.
Уровни не перекрывают, а складываются: хук из пользовательских настроек и хук из проекта на одном событии выполнятся оба. Посмотреть, что реально настроено и откуда, можно командой /hooks. Она открывает браузер хуков только для чтения.
Типы обработчиков и таймауты
ТипЧто делаетТаймаут по умолчаниюcommandshell-команда600 с (30 с на UserPromptSubmit)httpPOST с JSON события в теле600 сmcp_toolвызов инструмента MCP-сервера600 сpromptрешение принимает модель по промпту30 сagentэкспериментальный, проверку ведёт агент60 сДля запретов берите command: детерминированный скрипт не передумает. prompt удобен там, где правило трудно выразить регуляркой, но это снова суждение модели.
Хуки в Codex, Cursor и Gemini CLI
Свои хуки есть у всех трёх, но формат напрямую не совместим.
- Codex:
hooks.jsonили таблицы[hooks]вconfig.toml(~/.codex/или.codex/в репозитории). События с теми же именами, что у Claude Code:PreToolUse,PostToolUse,Stop,PreCompactи другие. Непроверенные хуки нужно одобрить через/hooks. Документация. - Cursor:
.cursor/hooks.jsonи~/.cursor/hooks.json, события в camelCase:beforeShellExecution,afterFileEdit,beforeMCPExecution. Документация. - Gemini CLI: блок в
settings.json, 11 событий, свои имена:BeforeTool,AfterTool,BeforeModel. Документация.
Скрипт-обработчик обычно переносится, конфиг вокруг него придётся переписать. Готовые хуки собраны в разделе Хуки.