> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Реагировать на события с помощью мода

> Обрабатывайте события Claude Code из мода: наблюдайте, переписывайте или отвечайте на вызовы инструментов, подсказки и ходы, фильтруйте события, которые обрабатывает hook, и планируйте для других модов.

Hook — это обработчик события: функция, которую Claude Code запускает при возникновении именованного события. Claude Code генерирует событие в каждой точке, где он собирается действовать, например, когда он запускает инструмент, отправляет подсказку, отправляет запрос к модели или начинает или завершает сеанс. Ваш hook запускается перед тем, как Claude Code действует, поэтому он может наблюдать событие, переписать его или ответить на него вместо Claude Code. Вы регистрируете hook с помощью [`on(eventName, handler)`](/docs/ru/plugins/mods/reference#the-hook-function).

Создайте свой [первый мод](/docs/ru/plugins/mods/create) перед тем, как начать здесь. Для каждого события и его точных полей см. [справочник](/docs/ru/plugins/mods/reference#events) или прочитайте [типы для вашей сборки](/docs/ru/plugins/mods/create#get-the-types-for-your-build).

<h2 id="how-a-hook-handles-an-event">
  Как hook обрабатывает событие
</h2>

Hook находится между событием и тем, что Claude Code сделает с ним, поэтому он может наблюдать событие, переписать его или ответить на него сам. Он получает три аргумента: [mods API](/docs/ru/plugins/mods/api) как `$`, событие как `e` и следующий обработчик как `next`. Обработчики события образуют цепочку middleware. `next(e)` вызывает следующий обработчик, который является hook другого мода или, в конце цепочки, собственным поведением Claude Code, и разрешается в результат. То, что ваш hook делает с `next`, определяет, какой из трёх вариантов он выполняет.

<h3 id="observe-an-event">
  Наблюдать событие
</h3>

Чтобы наблюдать событие без его изменения, выполните свою работу и верните `next(e)`. Этот hook регистрирует каждый инструмент, который Claude собирается использовать:

```javascript theme={null}
on('tool.call', async ($, e, next) => {
  // Запускается перед запуском инструмента
  $.ui.log('Claude is about to use ' + e.tool)
  // Передайте событие дальше без изменений
  return next(e)
})
```

Перед запуском каждого инструмента в расшифровке появляется тусклая строка, такая как `● my-mod: Claude is about to use Bash`, где `my-mod` — это имя вашего плагина. Инструмент запускается так же, как без мода.

Чтобы действовать после события, `await next(e)`, выполните свою работу и верните результат. Этот hook регистрирует каждый инструмент после его запуска:

```javascript theme={null}
on('tool.call', async ($, e, next) => {
  // Позвольте инструменту запуститься и дождитесь его результата
  const result = await next(e)
  // Запускается после запуска инструмента
  $.ui.log(e.tool + ' finished')
  // Верните результат без изменений
  return result
})
```

Строка теперь появляется после завершения каждого инструмента. Claude читает один и тот же результат в любом случае, потому что hook возвращает то, в чём разрешился `next(e)`.

<h3 id="rewrite-an-event">
  Переписать событие
</h3>

Чтобы изменить то, на что действует Claude Code, например текст подсказки, вызовите `next` с изменённой копией события. Само событие неизменяемо: оно заморожено на каждой глубине, и присваивание полю выбрасывает исключение. Этот hook обрезает каждую подсказку перед её отправкой:

```javascript theme={null}
on('prompt.submit', async ($, e, next) => {
  // Передайте копию события с изменённым текстом
  return next({ ...e, text: e.text.trim() })
})
```

Более поздние обработчики и Claude Code получают обрезанную подсказку и никогда не видят оригинал. Вы также можете изменить результат: `await next(e)`, затем верните копию результата с заменённым полем.

<h3 id="answer-an-event">
  Ответить на событие
</h3>

Чтобы обработать событие самостоятельно, верните результат без вызова `next`. Это короткозамыкает цепочку, поэтому более поздние моды и собственное поведение Claude Code не запускаются. Этот hook отказывает в каждой команде Bash:

```javascript theme={null}
on('tool.call', { tool: 'Bash' }, async () => {
  // Нет вызова next, поэтому команда никогда не запускается
  return { deny: 'Bash is turned off in this project. Use the file tools.' }
})
```

Когда Claude пытается выполнить команду Bash, команда не запускается, и Claude читает текст `deny` как результат инструмента. Каждое событие имеет свою форму результата, которую [справочник событий](/docs/ru/plugins/mods/reference#events) перечисляет.

<h3 id="filter-which-events-a-hook-handles">
  Фильтровать события, которые обрабатывает hook
</h3>

Чтобы запустить hook только для некоторых событий, передайте фильтр в качестве второго аргумента `on`. Claude Code называет фильтр matcher. Это объект, чьи поля сравниваются с полями события, и hook запускается только когда каждое поле совпадает. Поле может быть значением, массивом допустимых значений или регулярным выражением.

Каждая строка в этом примере регистрирует одну и ту же функцию, `hook`, для более узкого набора вызовов инструментов:

```javascript theme={null}
// Строка совпадает с одним значением: только вызовы Bash
on('tool.call', { tool: 'Bash' }, hook)
// Массив совпадает с любым значением в нём: вызовы Edit и Write
on('tool.call', { tool: ['Edit', 'Write'] }, hook)
// Регулярное выражение совпадает по шаблону: каждый инструмент одного сервера MCP
on('tool.call', { tool: /^mcp__github__/ }, hook)
```

`hook` запускается один раз для вызова Bash, Edit или Write и один раз для вызова инструмента, имя которого начинается с `mcp__github__`. Вызов любого другого инструмента, такого как Read, не совпадает ни с одним из трёх, поэтому `hook` не запускается для него.

Имя события может быть подстановочным символом. `'classic.*'` совпадает с каждым [событием hook настроек](#hook-the-settings-hook-events). `'*'` совпадает с каждым событием, кроме [событий телеметрии](/docs/ru/plugins/mods/reference#telemetry), которые вы подключаете по имени или как `'telemetry.*'`.

Регистрируйте каждое событие один раз для каждого matcher. Если вы вызовете `on` дважды для `session.start` без matcher, модуль не загружается с ошибкой `on("session.start") is registered twice without a matcher`. Поместите всё, что ваш мод делает при запуске сеанса, в один hook.

<h2 id="hook-what-claude-is-doing">
  Подключить то, что делает Claude
</h2>

Подключите эти события, чтобы увидеть или изменить вызов инструмента, подсказку или ход по мере их возникновения. Для каждого события и того, что может вернуть hook, см. [справочник событий](/docs/ru/plugins/mods/reference#events).

<h3 id="guard-or-change-a-tool-call">
  Охранять или изменять вызов инструмента
</h3>

Hook `tool.call` видит каждый инструмент, который Claude собирается использовать, поэтому он может отказать в вызове, изменить его аргументы или пропустить его. `tool.call` срабатывает, когда Claude Code собирается запустить инструмент, включая вызовы, которые делает подагент, и вызовы инструментов MCP. `e.tool` — это имя инструмента, а аргументы инструмента — это поля `e`, такие как `e.command` для Bash. Когда вы вызываете `next(e)`, Claude Code запускает проверку разрешений, а затем инструмент.

Этот hook отказывает в команде Bash, которая выполняет force-push, и объясняет Claude почему:

```javascript theme={null}
// Matcher ограничивает hook вызовами Bash, поэтому e.command — это команда оболочки
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
  if (/git push .*--force/.test(e.command)) {
    // Возврат без вызова next отвечает на событие, поэтому команда никогда не запускается
    return { deny: 'Force pushes are not allowed in this repository. Push to a new branch instead.' }
  }
  // Каждая другая команда переходит к проверке разрешений, а затем к Bash
  return next(e)
})
```

Когда Claude пытается выполнить `git push --force`, команда не запускается и не появляется запрос разрешения, потому что hook никогда не вызывает `next`. Claude читает текст `deny` как результат инструмента, поэтому напишите его как инструкцию, на которую Claude может действовать. Каждая другая команда Bash запускается так же, как без мода.

Чтобы действовать после запуска инструмента, `await next(e)`, выполните свою работу и верните то, что дал вам `next`. Этот hook регистрирует каждый файл `.mdx`, который Claude изменяет, с помощью [`$.ui.log`](/docs/ru/plugins/mods/api#show-something-without-starting-a-turn), который добавляет тусклую строку в расшифровку, которую Claude не читает:

```javascript theme={null}
on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
  // Дождитесь проверки разрешений и инструмента, и сохраните то, что они произвели
  const result = await next(e)
  // Отказанный вызов возвращается как { deny }, а неудачный имеет установленный isError
  const changed = !result.deny && !result.isError
  if (changed && e.file_path.endsWith('.mdx')) $.ui.log('Claude changed ' + e.file_path)
  // Верните результат как он пришёл, чтобы Claude читал то, что вернул инструмент
  return result
})
```

После того как Claude редактирует или записывает файл `.mdx`, тусклая строка в расшифровке называет файл. Ничего не регистрируется для другого вида файла или для вызова, который был отказан или не удался. Представление Claude о вызове не меняется, потому что hook возвращает результат, который он получил.

Чтобы изменить вызов, передайте изменённые аргументы `next`. Чтобы повторить вызов, вызовите `next(e)` снова: hook, который видит `isError` на первом результате, может запустить инструмент второй раз и вернуть этот результат. Чтобы ответить на вызов самостоятельно, верните объект с полем `result`, такой как `{ result: 'Skipped by my-mod' }`, без вызова `next`. Когда вы это делаете, запрос разрешения не появляется и инструмент не запускается, поэтому результат, который вы возвращаете, — это всё, что Claude узнает о том, что произошло.

Hooks в [управляемых настройках](/docs/ru/server-managed-settings) вашей организации запускаются перед любым hook `tool.call` мода, и блокировка от одного из них является окончательной.

<h4 id="hold-a-tool-call-until-the-user-decides">
  Удерживать вызов инструмента до тех пор, пока пользователь не решит
</h4>

Hook может приостановить вызов инструмента и спросить пользователя, что делать, прежде чем он продолжится. Hook `tool.call` может `await` перед вызовом `next` или возвратом, и вызов инструмента остаётся в ожидании до тех пор. Чтобы задать вопрос пользователю, вызовите `$.ui.ask`. Он показывает ваш вопрос над пронумерованным списком ваших вариантов в диалоге, который Claude использует, чтобы спросить вас что-то, и разрешается в метку, которую выбрал пользователь. После ваших вариантов диалог добавляет строку для ввода другого ответа и строку **Chat about this**.

Шаблон `RISKY` в этом примере совпадает с `rm -r`, `rm -rf`, `git reset --hard` и `git push` с `--force`, и он пропускает другие написания, такие как `git push -f`. Этот модуль спрашивает перед запуском команды Bash, которая совпадает с шаблоном:

```javascript theme={null}
const RISKY = /\brm\s+-rf?\b|\bgit\s+reset\s+--hard\b|\bgit\s+push\b.*--force/

export function register(on) {
  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
    // Пропустите каждую другую команду без вопроса
    if (!RISKY.test(e.command)) return next(e)
    // Начните с безопасного ответа, чтобы вопрос, на который никто не ответит, отказал в команде
    let answer = 'Refuse'
    try {
      // Вызов инструмента ждёт здесь, пока пользователь выберет один из двух ярлыков
      answer = await $.ui.ask('Run this command? ' + e.command, ['Run it', 'Refuse'])
    } catch {
      // Пользователь отклонил вопрос, или это запуск claude -p без кого-либо, кого можно спросить
    }
    if (answer !== 'Run it') {
      // Ответьте без вызова next, чтобы команда не запускалась
      return { deny: 'The user declined this command. Ask before trying a different approach.' }
    }
    return next(e)
  })
}
```

Когда Claude пытается выполнить команду, такую как `rm -rf build`, вопрос появляется с командой в нём, и команда ждёт ответа:

* **Пользователь выбирает Run it**: hook вызывает `next(e)`, и обычная проверка разрешений всё ещё запускается после неё
* **Пользователь выбирает Refuse**: команда не запускается, и Claude читает текст `deny`
* **Пользователь вводит ответ**: `$.ui.ask` разрешается в введённый текст. Hook сравнивает его с `Run it`, поэтому любой другой текст отказывает в команде.
* **Никто не отвечает**: `$.ui.ask` отклоняется, когда пользователь отклоняет вопрос или выбирает **Chat about this**, и в запуске `claude -p`, поэтому блок `catch` оставляет ответ на `Refuse`

Держите ожидание внутри вызова mods API, такого как `$.ui.ask`, потому что это время не учитывается в [10-секундном ограничении времени](/docs/ru/plugins/mods/reference#limits) hook. Время, потраченное на ожидание собственного обещания, учитывается. Claude Code пропускает hook, который истекает по времени, поэтому удерживаемая команда запустится.

<h3 id="rewrite-or-add-to-a-prompt">
  Переписать или добавить к подсказке
</h3>

Hook `prompt.submit` видит каждую подсказку перед началом хода, поэтому он может переписать текст или добавить к нему. `e.text` — это то, что было введено.

| Чтобы сделать это | Верните это |
| :- | :- |
| Переписать подсказку. Сообщение в расшифровке показывает новый текст. | `next({ ...e, text: newText })` |
| Добавить текст, который читает только Claude, после подсказки | `next({ ...e, context: [...(e.context ?? []), extraText] })` |
| Остановить отправку подсказки | `{ drop: 'the reason' }` |

Этот hook добавляет имя текущей ветки для Claude всякий раз, когда подсказка упоминает pull request:

```javascript theme={null}
on('prompt.submit', async ($, e, next) => {
  // Передайте подсказку, которая не упоминает pull request, как она есть
  if (!/\bPR\b|pull request/i.test(e.text)) return next(e)
  const git = await $.process.run(['git', 'branch', '--show-current'])
  // Вне репозитория git команда не удаётся, поэтому нет ветки для добавления
  if (git.exitCode !== 0) return next(e)
  // Сохраните любой контекст, который добавил более ранний hook, и добавьте ещё одну строку для Claude
  return next({ ...e, context: [...(e.context ?? []), 'Current branch: ' + git.stdout.trim()] })
})
```

Когда вы отправляете подсказку, такую как `open a PR for this change`, ваше сообщение выглядит одинаково в расшифровке, и Claude также читает строку, такую как `Current branch: feature/auth` после неё. Подсказка, которая не упоминает pull request, проходит без изменений, и `git` не запускается.

[Другие события](/docs/ru/plugins/mods/reference#prompts-and-what-claude-reads) охватывают остальное, что читает Claude: `prompt.section` для каждого раздела системной подсказки, `prompt.context` для контекста, отправленного с первым сообщением, и `skill.prompt` для текста навыка. Текст из этих hooks, который меняется между запросами, [делает кэш подсказок недействительным](/docs/ru/prompt-caching).

<h3 id="follow-a-turn">
  Следить за ходом
</h3>

Ход — это всё, что Claude делает в ответ на одну подсказку. Подключите `turn.start`, `turn.step` и `turn.complete`, чтобы следить за одним:

| Событие | Когда оно срабатывает | Что может делать hook |
| :- | :- | :- |
| `turn.start` | Ход начинается | Наблюдать. `e.turnId` идентифицирует ход в двух других событиях. |
| `turn.step` | Claude Code собирается отправить один запрос к модели. Ход с вызовами инструментов имеет несколько. `e.agentId` установлен для запроса подагента. | Прочитайте использование токенов каждого запроса, отправьте его другой модели с `next({ ...e, model })` или ответьте без вызова модели |
| `turn.complete` | Ход закончился, включая ход, который пользователь прервал, где `e.isAborted` — `true`. `e.answer` — это финальный текст Claude, `e.durationMs` — сколько времени это заняло, и `e.usage` — итоги токенов хода. Ход подагента срабатывает с установленным `e.agentId`. | Наблюдать или вернуть объект с полем `text`, такой как `{ text: 'Done in 12 seconds' }`, чтобы показать строку под ответом |

Напишите hook `turn.step` как асинхронный генератор, потому что событие потоковое. `yield* next(e)` пересылает ответ по мере его потока и вычисляется в завершённый результат. Этот hook регистрирует, сколько каждого запроса Claude API обслужил из [кэша подсказок](/docs/ru/prompt-caching):

```javascript theme={null}
// function* делает hook генератором, который может передавать ответ по частям
on('turn.step', async function* ($, e, next) {
  // Отправьте запрос, пересылайте каждую часть по мере её поступления и сохраняйте завершённый результат
  const result = yield* next(e)
  // Пропустите результат, который не сообщает количество токенов
  if (result.usage) {
    $.ui.log('cache read ' + result.usage.cache_read_input_tokens + ' · wrote ' + result.usage.cache_creation_input_tokens)
  }
  // Верните результат без изменений, чтобы ход продолжался как обычно
  return result
})
```

Ответ Claude потоком идёт на экран так же, как без мода. После завершения каждого запроса тусклая строка в расшифровке даёт количество токенов, прочитанных из кэша, и количество записанных в него. Ход с вызовами инструментов имеет несколько запросов, поэтому он добавляет несколько строк.

`result.usage` содержит четыре количества токенов, которые сообщает Claude API для запроса, плюс `model`, который ответил: `input_tokens`, `output_tokens`, `cache_read_input_tokens` и `cache_creation_input_tokens`. Hook запускается и для запросов подагентов, поэтому проверьте `e.agentId`, когда вам нужна только основная беседа.

<h3 id="hook-the-settings-hook-events">
  Подключить события hook настроек
</h3>

Hooks настроек — это command, HTTP, prompt и agent hooks, которые вы настраиваете в файлах настроек. Каждое [событие hook настроек](/docs/ru/hooks#hook-events), такое как `Stop`, `SessionEnd` или `PostToolUse`, также является событием с именем `classic.` с последующим именем события hook настроек, такое как `classic.Stop`. `e` — это JSON, который получает hook настроек на stdin, включая `transcript_path`.

Этот hook использует `Stop`, который срабатывает, когда Claude заканчивает отвечать, чтобы зарегистрировать, где сохраняется расшифровка сеанса:

```javascript theme={null}
on('classic.Stop', async ($, e, next) => {
  // e имеет те же поля, которые hook Stop в файле настроек читает из stdin
  $.ui.log('Transcript saved at ' + e.transcript_path)
  // Передайте событие дальше, чтобы hooks Stop в ваших файлах настроек всё ещё запускались
  return next(e)
})
```

Каждый раз, когда Claude заканчивает отвечать, тусклая строка в расшифровке даёт путь файла расшифровки.

<h2 id="run-alongside-other-mods">
  Запускаться рядом с другими модами
</h2>

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

<h3 id="the-order-mods-run-in">
  Порядок, в котором запускаются моды
</h3>

Hooks на одно и то же событие образуют одну цепочку middleware. Каждый `next` мода вызывает hook следующего мода, и последний `next` достигает собственного поведения Claude Code. Первый мод является самым внешним: он видит событие перед другими и результат после них, и он решает, запускаются ли другие вообще. Более поздний мод не может остановить более ранний от просмотра события.

Claude Code упорядочивает цепочку по тому, откуда берётся каждый мод:

1. Встроенная защита `sec-default@builtin`, мод, встроенный в Claude Code, который `/plugin` перечисляет как `cc-plugin-sec-default`, где [он загружается](/docs/ru/plugins/mods/admin#know-what-happens-by-default), моды, которые ваша организация перечисляет в [`prependPlugins`](/docs/ru/plugins/mods/admin#install-your-organizations-mods), а затем любой другой мод, который считается модом вашей организации и не находится в `appendPlugins`
2. Моды, которые вы устанавливаете
3. Моды, которые ваша организация перечисляет в `appendPlugins`
4. Другие моды, встроенные в Claude Code

Среди модов, которые вы устанавливаете, мод запускается перед модами, которые он перечисляет в `dependencies` в своём манифесте. В одном модуле hooks запускаются в порядке, в котором `register` вызвал `on`.

<h4 id="where-settings-hooks-run-in-the-order">
  Где hooks настроек запускаются в порядке
</h4>

Hooks `PreToolUse`, настроенные в файлах настроек, также запускаются во время вызова инструмента в фиксированных точках цепочки модов:

* **Hooks `PreToolUse` из управляемых настроек**: запускаются перед hook `tool.call` первого мода, и блокировка от одного из них является окончательной, поэтому ни один мод не видит вызов.
* **Hooks `PreToolUse` из каждого другого файла настроек и из `hooks/hooks.json` плагинов**: запускаются после того, как последний мод вызовет `next`, как часть собственного поведения Claude Code. Мод, который отвечает на `tool.call` без вызова `next`, не позволяет им запускаться, и мод, который вызывает `next`, видит их решение в результате, который он возвращает.

[`tool.check`](/docs/ru/plugins/mods/reference#tools) — это событие, где Claude Code решает, может ли вызов инструмента запуститься. Оно срабатывает после этих hooks и правил разрешений, и `next(e)` разрешается в их решение. Hook на `tool.check` может вернуть другое решение, такое как `{ decision: 'allow' }`, поэтому он может одобрить вызов, который hook из второй группы заблокировал. [Расширить разрешения с помощью hooks](/docs/ru/permissions#extend-permissions-with-hooks) перечисляет, какие решения имеют приоритет над модом.

<h3 id="handle-a-hook-that-fails">
  Обработать hook, который не удаётся
</h3>

Hook, который не удаётся, не нарушает сеанс, и вы можете решить, что происходит вместо этого. Когда hook без обработчика `.catch` выбрасывает исключение, истекает по времени или возвращает результат неправильной формы, что происходит дальше, зависит от того, вызвал ли он `next`:

* **Он не удался перед вызовом `next`**: Claude Code пропускает его, и следующий обработчик запускается на его месте
* **Он не удался после разрешения `next`**: этот результат стоит, и ничего не запускается второй раз

Одна строка называет мод, событие и причину, такую как `my-mod: tool.call hook skipped: threw Error: boom`. Где вы это читаете, зависит от сеанса, как [Узнать, почему мод ничего не делает](/docs/ru/plugins/mods/troubleshoot#find-out-why-a-mod-does-nothing) перечисляет. Hook `ui.render`, чей рисунок не проходит валидацию, сообщается иначе, как [Построить дерево из элементов](/docs/ru/plugins/mods/interface#build-a-tree-from-elements) описывает.

Чтобы сделать hook, который блокирует вызовы, не удаётся закрыто, добавьте обработчик ошибок `.catch`, который отвечает на его месте. Здесь `guard` — это ваша функция hook:

```javascript theme={null}
// on возвращает регистрацию, и .catch присоединяет обработчик к этому одному hook
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
  // next.error.kind — это 'throw' или 'timeout', что говорит, как guard не удался
  return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
})
```

Пока `guard` работает, обработчик никогда не запускается. Когда `guard` выбрасывает исключение или истекает по времени при вызове Bash, Claude Code вызывает обработчик с тем же событием. Обработчик возвращает `{ deny }`, поэтому команда не запускается, и Claude читает текст с `throw` или `timeout` в конце. Без обработчика Claude Code пропустил бы `guard` и запустил бы команду. Обработчик имеет [одну секунду](/docs/ru/plugins/mods/reference#limits) для ответа.

<h2 id="next-steps">
  Следующие шаги
</h2>

* [Использовать mods API](/docs/ru/plugins/mods/api): добавлять команды и инструменты, вызывать модель и запускать работу по таймеру
* [Рисовать в интерфейсе](/docs/ru/plugins/mods/interface): показывать то, что собирают ваши hooks, в панели или над подсказкой
* [Тестировать мод](/docs/ru/plugins/mods/test): вызывать любое из этих событий из теста
* [Справочник модов](/docs/ru/plugins/mods/reference): каждое событие, каждый метод mods API и ограничения
