раздел 06 · подстраница 4
Отладка hooks
Hooks ломаются молча. Hook не сработал, JSON не валиден, переменная пустая - Claude молча идёт дальше, и вы не сразу понимаете, что hook вообще не запускался. Здесь - чек-лист, как быстро найти причину.
Зачем
Самая частая жалоба: "написал hook, он не работает". В 90% случаев причина - в одной из четырёх типовых ошибок. Если знать, куда смотреть, диагностика занимает 2 минуты, а не час.
Куда смотреть в первую очередь
1. Логи Claude Code
ls -la ~/.claude/logs/
tail -f ~/.claude/logs/claude.log
Там видно, какие hooks триггерились, с каким exit-кодом, что писали в stderr.
2. Запустить Claude с --debug
claude --debug
В debug-режиме CLI печатает прямо в терминал каждый запуск hook с командой, env, exit-кодом и stderr.
[hook] PostToolUse matcher=Edit|Write
[hook] cmd: .claude/hooks/format.sh
[hook] env: CLAUDE_TOOL_NAME=Edit CLAUDE_FILE_PATHS=src/app.py
[hook] exit: 0
[hook] stderr: (empty)
3. Валидировать settings.json
cat ~/.claude/settings.json | python3 -m json.tool
# или
jq . ~/.claude/settings.json
Если выводит ошибку - JSON битый, hooks не работают вообще. Самая частая причина:
- Лишняя запятая после последнего элемента массива/объекта.
- Незакрытая скобка.
- Кавычки внутри
commandбез экранирования (\").
Топ-5 причин "hook не работает"
Причина 1. Битый JSON
Симптом: hooks не срабатывают вообще, никакие. Claude даже не пишет ошибку.
Решение:
jq . ~/.claude/settings.json
# покажет место ошибки
Причина 2. Не тот уровень файла
Симптом: hook есть в ~/.claude/settings.json, но в проекте не срабатывает.
Возможные причины:
- В проекте есть
.claude/settings.jsonкоторый переопределяет структуру (для permissions). - Или вы добавили hook в
settings.local.json, а он не подцепляется (опечатка в имени файла).
Решение: проверить, какие три файла существуют:
ls -la ~/.claude/settings.json .claude/settings.json .claude/settings.local.json 2>/dev/null
Причина 3. matcher не совпадает
Симптом: hook должен срабатывать на Edit, но не срабатывает.
Возможные причины:
- Опечатка:
"matcher": "edit"вместо"Edit"- регистр важен. - Используется
MultiEditилиWrite, а matcher толькоEdit.
Решение: расширьте matcher или используйте *:
"matcher": "Edit|Write|MultiEdit"
Причина 4. Hook падает с exit 1 без сообщения
Симптом: что-то блокируется, но непонятно почему.
Решение: всегда писать в stderr перед exit:
[[ "$CLAUDE_FILE_PATHS" == *".env"* ]] && {
echo "blocked: tried to read .env file" >&2
exit 1
}
exit 0
Тогда в --debug режиме сразу видно, что и почему.
Причина 5. Переменные окружения пустые
Симптом: $CLAUDE_FILE_PATHS пуст или $CLAUDE_TOOL_NAME undefined.
Возможные причины:
- Hook повешен на событие, где этой переменной нет (например,
$CLAUDE_FILE_PATHSтолько в Edit/Write, не в Bash). - В JSON используется
$CLAUDE_FILE_PATHSв двойных кавычках без экранирования - shell не получит переменную.
Решение: дампьте env прямо из hook:
env | grep CLAUDE > /tmp/hook-env-debug.txt
И смотрите, что реально пришло.
Как тестировать hook без перезапуска Claude
Hooks подхватываются на лету, перезапуск Claude не нужен. Но триггерить событие из обычного Claude-чата - неудобно. Лайфхак - имитировать вызов вручную:
# симулируем PostToolUse для Edit
export CLAUDE_TOOL_NAME=Edit
export CLAUDE_FILE_PATHS="src/app.py"
export CLAUDE_SESSION_ID="test"
export CLAUDE_PROJECT_DIR="$PWD"
bash .claude/hooks/format.sh
echo "exit code: $?"
Если в терминале отработало - в Claude отработает тоже.
Изоляция: где проблема, в hook или в Claude
Простой тест: положите явно работающий hook:
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "echo 'hook fired' > /tmp/hook-test.log" }]
}
]
}
}
После следующего Stop в Claude:
cat /tmp/hook-test.log
Если файла нет - проблема в Claude/конфиге. Если есть - значит hooks вообще работают, дело в вашей конкретной команде.
Чек-лист при отладке
- [ ]
jq . settings.json- валидный JSON? - [ ]
claude --debug- hook вообще триггерится? - [ ] Команда сама по себе работает в терминале?
- [ ] matcher покрывает реальные имена инструментов?
- [ ] Переменные доступны на этом событии?
- [ ] Есть
exit 0в конце успешной ветки? - [ ] Сообщение в stderr при exit 1?
Антипаттерны
- Молчаливый
exit 1- вы не поймёте причину блока. - Hook без
set -eили с глотанием ошибок - тихо ломается на середине. - Отладка через "печатаю в stdout" - stdout уходит в Claude как ответ инструмента и иногда ломает поток. Пишите в stderr или в файл.
- Долгие команды без
timeout- в одном из 100 запусков hook зависнет, и весь Claude встанет.
Полезные ссылки
- Hooks reference - официальная справка
- Debug flag - подробности
--debug