Claude Code hook написал ошибку, но команда всё равно запустилась

Команда добавила hook, чтобы агент не запускал опасную shell-команду. Скрипт пишет «запрещено» и завершает работу с кодом 1, но Claude Code всё равно выполняет команду. Логика проверки могла быть правильной; неверным оказался способ передать решение. Для command hook код 1 обычно означает ошибку самого hook, а не отказ в выполнении действия.

Поставьте проверку до инструмента

Используйте событие PreToolUse с matcher Bash (и отдельно PowerShell, если этот инструмент доступен в вашей конфигурации). Hook получает JSON во входном потоке; поле tool_name указывает инструмент, а команда лежит в tool_input.command. Проверка в PostToolUse уже не может предотвратить вызов: она выполняется после команды. Для блокировки до запуска command hook пишет причину в stderr и выходит с кодом 2. Код 0 означает, что hook не запрещает действие; обычный механизм разрешений Claude Code продолжает работать.

Пример без реального удаления проверяет только строку команды с явным маркером BLOCK_ME. Сохраните файл .claude/hooks/check-command.js:

const fs = require('node:fs');
const input = JSON.parse(fs.readFileSync(0, 'utf8'));
if (input.tool_name === 'Bash' &&
    String(input.tool_input?.command ?? '').includes('BLOCK_ME')) {
  process.stderr.write('Blocked by project hook\n');
  process.exit(2);
}

В .claude/settings.json укажите hooks.PreToolUse, matcher Bash, обработчик type: "command", command: "node" и args: ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"]. Такая конфигурация соответствует текущему формату command hook. Попросите Claude Code выполнить безвредную команду с маркером, например echo BLOCK_ME. Ожидаемый результат — tool call заблокирован, а текст stderr показан как причина. Для echo OK hook не возражает.

Отказ и поломка hook — разные исходы

Если файл hook отсутствует, JSON не парсится или процесс зависает, по умолчанию это может быть неблокирующей ошибкой. Для политики, которая должна закрываться при поломке, задайте обработчику onFailure: "block" (Claude Code v2.1.295+). Это не замена коду 2 для обычного решения «запретить команду»: код 2 — осознанный отказ, onFailure — поведение при сбое самой проверки. В журнале теста различайте оба случая.

Строковый поиск по команде — учебный тест механизма hook, а не защита от всех способов выполнить действие. Реальная политика должна разбирать допустимые аргументы и учитывать другие инструменты, которые умеют менять файлы или запускать код. После настройки проверьте hook на конкретном наборе разрешённых и запрещённых входов, а не на одном echo. Если правило касается операций записи, добавьте проверку прав инструмента на стороне среды.

В платном курсе Vibecoding: Claude Code, Codex, Cursor и agentic development вы настраиваете coding agent, задаёте границы работы и проверяете изменения. Hook становится полезным, когда его поведение воспроизводимо: команда либо блокируется до запуска, либо проходит обычную проверку прав. Откройте программу курса, чтобы встроить такие проверки в командный workflow.

Продолжить на практике

  • Vibecoding: Claude Code, Codex, Cursor и agentic development ↗

    Практический курс по vibecoding и agentic development: Claude Code, Codex, Cursor и современные coding agents в реальной разработке. Вы научитесь запускать feature workflow через AI, работать с кодовой базой, ускорять debugging, tests и refactoring, делегировать агенту заметную часть разработки и при этом сохранять контроль над качеством, архитектурой и итоговым результатом.