0.4.11
08 · Техническая документация

08. Хуки (Hooks)

Хуки — точки расширения жизненного цикла сессии. Они позволяют запустить произвольную команду в ответ на событие (например, перед каждым tool-вызовом или после его завершения).

Реализация — крейт hooks/. Конфигурация — в файле hooks.json или в секции [hooks] файла config.toml, по слоям конфигурации (system / user / project / managed / плагины).

1. Типы событий

Событие Когда срабатывает
SessionStart При старте сессии (после загрузки конфига)
UserPromptSubmit Когда пользователь отправил сообщение в composer
PreToolUse Перед выполнением tool'а (shell, file edit, mcp)
PermissionRequest При запросе разрешения от пользователя (можно автоматически одобрить/отказать)
PostToolUse После выполнения tool'а
Stop При завершении ответа модели

Имена событий используются как ключи секции [hooks] (например, [[hooks.PreToolUse]]) — см. ниже.

2. Типы обработчиков

Тип Что делает
command Запускает shell-команду (с таймаутом)
prompt Не реализован. Объявление такого обработчика приводит к предупреждению «prompt hooks are not supported yet», и он пропускается.
agent Не реализован. Объявление приводит к предупреждению «agent hooks are not supported yet», и он пропускается.

На текущий момент реально работает только обработчик command.

3. Синтаксис в config.toml

toml
[[hooks.PreToolUse]]
matcher = "Bash"            # опционально: regex по имени инструмента
hooks = [
  { type = "command",
    command = "cat >> ~/tool.log",
    timeout = 5,
    statusMessage = "Logging tool call..." },
]

[[hooks.PostToolUse]]
matcher = "Edit|Write"
hooks = [
  { type = "command",
    command = "prettier --check ." },
]

[[hooks.SessionStart]]
hooks = [
  { type = "command",
    command = "git status --short" },
]

Каждый [[hooks.<Event>]] — отдельная группа с опциональным matcher и массивом обработчиков hooks. Несколько [[hooks.PreToolUse]] блоков обрабатываются в порядке объявления.

То же самое можно задать в файле hooks.json соответствующего слоя конфигурации:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "cat >> ~/tool.log", "timeout": 5 }
        ]
      }
    ]
  }
}

4. Поля обработчика command

toml
{ type = "command",
  command = "shell-string",        # обязательно: команда для shell
  timeout = 10,                    # опционально, секунд (по умолчанию 600, минимум 1)
  statusMessage = "Doing thing..." # опционально, показывается в TUI пока работает
}

Поддерживаются только поля command, timeout, async, statusMessage. Полей args, workdir, env нет.

async не поддерживается. Поле распознаётся, но если задано async = true, обработчик пропускается с предупреждением «async hooks are not supported yet». Все хуки выполняются синхронно.

Как команда получает данные события

Данные события передаются команде JSON-ом в stdin (не через env-переменные). Команда запускается в системном shell ($SHELL -lc на Unix, cmd /C на Windows); сама строка command передаётся shell как есть, а JSON-описание события пишется в stdin процесса.

Основные поля JSON (зависят от события, см. hooks/src/schema.rs):

  • session_id — идентификатор сессии
  • turn_id — идентификатор текущего хода (расширение AstraCode)
  • transcript_path — путь к транскрипту (или null)
  • cwd — рабочая директория
  • hook_event_name — имя события ("PreToolUse", "PostToolUse" и т.п.)
  • model, permission_mode
  • tool_name — имя инструмента (для PreToolUse / PermissionRequest / PostToolUse)
  • tool_input — аргументы инструмента (JSON)
  • tool_response — результат (только для PostToolUse)
  • tool_use_id — id вызова (для PreToolUse / PostToolUse)
  • prompt — текст сообщения (только для UserPromptSubmit)
  • source — источник старта сессии: startup / resume / clear (только для SessionStart)
  • stop_hook_active, last_assistant_message (только для Stop)

Пример чтения tool_name в shell:

toml
[[hooks.PreToolUse]]
matcher = "Bash"
hooks = [
  { type = "command",
    command = "jq -r .tool_name >> ~/tool.log" },
]

Обработчик может вернуть в stdout JSON для управления решением (например, decision, permissionDecision, additionalContext) — см. соответствующие output-схемы в hooks/src/schema.rs. Простой случай: ненулевой exit code блокирует tool.

5. Matcher

matcher — это regex по имени инструмента (не DSL с операторами). Логика — в hooks/src/events/common.rs:

  • Пустая строка или "*" — совпадает со всеми инструментами.
  • Строка только из [A-Za-z0-9_|] трактуется как точное совпадение, где | задаёт альтернативы (например, "Edit|Write" совпадает с Edit или Write, но не с Bash).
  • Любой другой паттерн трактуется как полноценный regex (например, "^Bash$", "mcp__memory__.*").
  • Невалидный regex отклоняется на этапе обнаружения с предупреждением.

matcher учитывается только для событий PreToolUse, PermissionRequest, PostToolUse, SessionStart. Для UserPromptSubmit и Stop он игнорируется (всегда совпадает).

Имена инструментов — в стиле Claude: Bash, Edit, Write, mcp__<server>__<tool> (например, mcp__memory__create_entities).

Примеры matcher'ов:

toml
matcher = "Bash"                      # точное совпадение с Bash
matcher = "Edit|Write"                # Edit или Write
matcher = "^Bash$"                    # regex, якоря
matcher = "mcp__server__.*"           # все инструменты MCP-сервера
matcher = "*"                         # любой инструмент

Для SessionStart matcher сравнивается с источником запуска, например "startup|resume".

6. Где лежат хуки

Хуки собираются из всех слоёв конфигурации (hooks/src/engine/discovery.rs):

  • hooks.json в папке конфигурации слоя (system / user / project).
  • Секция [hooks] в config.toml соответствующего слоя.
  • Managed-хуки из требований (managed requirements), включая директорию для текущей платформы.
  • Хуки плагинов — плагины регистрируют хуки через свой манифест; им дополнительно прокидываются env-переменные PLUGIN_ROOT, PLUGIN_DATA (и совместимые CLAUDE_PLUGIN_ROOT, CLAUDE_PLUGIN_DATA).

Если в одном слое заданы и hooks.json, и секция [hooks] в config.toml, оба будут загружены, но появится предупреждение с рекомендацией оставить одно представление.

Директорий вида ~/.astracode/hooks/<platform>/<event>/*.toml нет. Хуки задаются именно в hooks.json или в секции [hooks].

7. Примеры

Блокировать rm -rf

toml
[[hooks.PreToolUse]]
matcher = "Bash"
hooks = [
  { type = "command",
    command = "jq -r .tool_input.command | grep -qE 'rm\\s+-rf?' && exit 1 || exit 0",
    timeout = 2 },
]

Ненулевой exit code блокирует tool.

Линтер после правок файлов

toml
[[hooks.PostToolUse]]
matcher = "Edit|Write"
hooks = [
  { type = "command",
    command = "prettier --check . || true",
    timeout = 30 },
]

Git status в начале каждой сессии

toml
[[hooks.SessionStart]]
hooks = [
  { type = "command",
    command = "git status --porcelain > /tmp/astracode_session_start.txt",
    timeout = 5 },
]

8. Логирование

Hook-выполнения логируются в лог TUI. Для детального лога:

bash
RUST_LOG=astracode_hooks=debug astracode

9. Ограничения и предостережения

  • Timeout ограничивает время выполнения команды (по умолчанию 600 с, минимум 1 с). По истечении процесс убивается.
  • Только синхронные хуки: async = true пока не поддерживается.
  • Только обработчик command: prompt и agent пока не реализованы.
  • Bash injection: tool_input и подобные данные приходят в stdin как JSON — парсите их (например, jq), а не интерполируйте в shell вслепую.
  • Хуки выполняются с теми же правами, что и сам AstraCode. Не запускайте недоверенные команды через хуки.

10. Файлы кода

  • Запуск команды (stdin/таймаут): astracode-rs/hooks/src/engine/command_runner.rs
  • Обнаружение и загрузка хуков по слоям: astracode-rs/hooks/src/engine/discovery.rs
  • Matcher (regex по имени инструмента): astracode-rs/hooks/src/events/common.rs
  • JSON-схемы входа/выхода: astracode-rs/hooks/src/schema.rs
  • Конфигурация хуков (поля обработчиков): astracode-rs/config/src/hook_config.rs