English version

Автор: abavyka

Хук — команда, яку Claude Code запускає сам у потрібний момент, а не тому що модель так вирішила.

Що це

Ти пишеш у налаштуваннях: на таку подію запусти таку команду. Подія — це момент у роботі Claude Code: перед викликом tool, після нього, коли Claude чекає на тебе, коли закінчив відповідь, старт сесії, compaction, всього понад три десятки.

Різниця з CLAUDE.md в тому, що CLAUDE.md — порада, і модель може її не виконати. Хук спрацьовує завжди. Тому раніше і писав «що критично — хуки».

Найчастіше хук — shell-команда ("type": "command"). Ще є:

  • http — POST з даними події на твій URL
  • mcp_tool — виклик tool на підключеному MCP-сервері
  • prompt — одне питання до моделі, яка вирішує «так/ні», коли правилом не описати
  • agent — субагент, який може сам почитати файли і запустити команди перед рішенням (експериментальний)

Як влаштований

Конфіг у три рівні: подія → matcher (на який tool, наприклад Bash чи Edit|Write) → команди.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
        ]
      }
    ]
  }
}

Це приклад з доки: після кожного редагування файлу Prettier його форматує.

На вхід (stdin) хук отримує JSON: tool_name, tool_input (для Bash там command, для Edit — file_path), cwd, session_id.

Відповідає хук exit code:

  • 0 — все ок
  • 2 — заблокувати дію, stderr іде Claude як пояснення, чому ні
  • будь-який інший — помилка хука, але дія йде далі

Тобто exit 1 нічого не блокує. Хочеш заборону — тільки exit 2.

Або хук пише JSON у stdout. Для PreToolUse це:

  • permissionDecision — allow, deny, ask або defer
  • updatedInput — підмінити аргументи tool перед запуском
  • additionalContext — докинути Claude текст поруч з результатом

Де живуть

  • ~/.claude/settings.json — усі твої проєкти
  • .claude/settings.json — проєкт, комітиться, вся команда отримує ті самі хуки
  • .claude/settings.local.json — проєкт, тільки ти
  • ще в плагінах (hooks/hooks.json), у frontmatter скілів і субагентів, і в managed settings від адміна

Хуки з усіх рівнів складаються, а не перетирають один одного. Всі, що підійшли під подію, запускаються паралельно.

  • /hooks — подивитись усі хуки по подіях (тільки перегляд)
  • "disableAllHooks": true — вимкнути всі. Вимкнути один, лишивши його в конфігу, не можна

Мої

  • guard-main — у репо цієї вікі, .claude/settings.json, PreToolUse на Bash. Не дає Claude закомітити чи запушити в main і змерджити PR. Це воркераунд: на безкоштовному GitHub у приватного репо нема branch protection
  • peon-ping — Notification, PermissionRequest, PostToolUseFailure. Голос пеона з Warcraft III, коли сесії треба моя увага. Корисний прикол
  • cc-status — ставить сама iTerm2 (Claude Code Integration). Показує в табі статус сесії: working, waiting, idle. Я мало юзаю, по суті це просто UI
  • rtk — PreToolUse на Bash, переписує команду через updatedInput: git status → rtk git status, і Claude бачить стиснутий вивід

У guard-main є обгортка на sh, яка без Node 24 блокує коміт і пуш через exit 2. Без неї хук на Node 20 падав би з помилкою, а помилка, як вище, нічого не блокує.

Що ще можна

Приклади з доки і те, що згадував раніше:

  • Prettier після кожного Edit|Write — конфіг вище
  • Заборона редагувати .env, package-lock.json і все в .git/ — PreToolUse на Edit|Write, скрипт дивиться file_path і робить exit 2. Claude отримує пояснення і шукає інший шлях
  • Повернути важливе після compaction — SessionStart з matcher compact, усе, що команда виведе в stdout, лягає в контекст. Наприклад git log --oneline -5 чи «юзаємо pnpm, не npm»
  • Typecheck після кожного редагування, а не проханням — PostToolUse на Edit|Write з npx tsc --noEmit 1>&2 || exit 2. На PostToolUse exit 2 нічого не відкочує (файл уже змінено), але помилки tsc ідуть Claude, і він їх фіксить
  • Не відпускати Claude, поки тести не зелені — Stop-хук типу agent: субагент запускає тести, і якщо вони падають, Claude працює далі
  • Фільтр виводу — хук відсікає з лога на 10 000 рядків усе, крім ERROR, до того, як це побачить Claude. Сотні токенів замість десятків тисяч. rtk якраз про це

Підводні камені

  • Хук — не замок. Таймаут чи помилка хука дію не блокують, якщо не стоїть onFailure: "block" (з 2.1.295). Для жорсткого allow/deny є permissions
  • Зате deny з хука блокує навіть у bypassPermissions
  • Два хуки з updatedInput на той самий tool — переможе той, що закінчив останнім, тобто випадковий
  • Файли, які ти сам додав через @, йдуть у промпт без виклику tool, тож PreToolUse на Read їх не бачить
  • Stop-хук, який блокує 8 разів підряд без жодного tool між ними, Claude Code перебиває і закінчує відповідь
  • Хук запускає будь-яку команду з твоїми правами. Хуки з .claude/settings.json чужого репо працюють у claude -p навіть без trust dialog. Склонував — подивись, що там у хуках

Почитати