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
[[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 соответствующего слоя конфигурации:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "cat >> ~/tool.log", "timeout": 5 }
]
}
]
}
}
4. Поля обработчика command
{ 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_modetool_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:
[[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'ов:
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
[[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.
Линтер после правок файлов
[[hooks.PostToolUse]]
matcher = "Edit|Write"
hooks = [
{ type = "command",
command = "prettier --check . || true",
timeout = 30 },
]
Git status в начале каждой сессии
[[hooks.SessionStart]]
hooks = [
{ type = "command",
command = "git status --porcelain > /tmp/astracode_session_start.txt",
timeout = 5 },
]
8. Логирование
Hook-выполнения логируются в лог TUI. Для детального лога:
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