Work with hooks
Use the hook system when you want to register handlers that fire automatically at Claude Code lifecycle events, execute hook actions against a context, or inspect the execution log for a running session.
Prerequisites
- Access to the project source code
- Python
Callableobjects or YAML configuration ready for the handlers you want to register
Steps
-
Create a
HookRegistry. InstantiateHookRegistryfromhooks. If you already have aHookConfigloaded from YAML, pass it to the constructor; otherwise leave the argument empty and add hooks programmatically.from hooks import HookRegistry, HookConfig config = HookConfig.from_yaml("hooks.yaml") # optional registry = HookRegistry(config=config) -
Register a handler for an event. Call
registry.register()with the targetHookEvent, your callable handler, and an optionalHookMatcherif the hook should fire only when the context meets specific conditions. The method returns ahandler_idstring — save it if you need to remove the handler later.from hooks import HookEvent handler_id = registry.register( event=HookEvent.POST_TOOL, handler=my_handler, description="Log tool output", priority=10, ) -
Fire the event. Call
registry.fire()to dispatch the event asynchronously, orregistry.fire_sync()when you need results in a synchronous context. Pass acontextdict with any data your handlers require.results = registry.fire_sync( HookEvent.POST_TOOL, context={"tool": "bash", "output": "..."}, ) -
Inspect results and the execution log.
fireandfire_syncboth return a list ofdict[str, Any]— one entry per handler that ran. To review the full history of dispatched events, callregistry.get_execution_log(). Filter by event type with theevent_filterparameter, or limit the number of entries withlimit.log = registry.get_execution_log(limit=50, event_filter=HookEvent.POST_TOOL) stats = registry.get_stats() -
Remove a handler when it is no longer needed. Call
registry.unregister()with thehandler_idreturned in step 2. The method returnsTrueif the handler was found and removed.removed = registry.unregister(handler_id) -
Run the related tests. Verify your handlers behave correctly and that no existing hooks regressed.
pytest -k "hooks"
Load or save configuration from YAML
If you prefer to manage hooks declaratively, use HookConfig directly:
HookConfig.from_yaml(yaml_path)— load a complete hook configuration from a YAML file.config.add_hook(event, hook, matcher, priority)— add aHookDefinitionto an existing config object.config.to_yaml(yaml_path)— write the current configuration back to disk.config.get_hooks_for_event(event)— retrieve allHookRuleobjects registered for a specificHookEvent.
Once the config is ready, load it into a registry with registry.load_config(config).
Execute a hook directly
To run a single HookDefinition outside the registry, use HookExecutor or its synchronous counterpart HookExecutorSync. Supply a dict of Python callables keyed by handler name if your hook definitions reference Python handlers.
from hooks import HookExecutor
from hooks import HookDefinition
executor = HookExecutor(python_handlers={"my_handler": my_handler})
result = executor.execute(hook_definition, context={"key": "value"})
Verify the task succeeded
The task is complete when:
registry.fire_sync()returns a non-empty list and each entry contains the keys your handler populates.registry.get_execution_log()shows the expected event entries with no error fields.registry.get_stats()reflects the correct count of registered handlers and fired events.pytest -k "hooks"passes with no failures.
Unresolved references
Auto-generated by attune-author fact-check. Review and either fix the source code, fix this doc, or add an override.
| Location | Severity | Issue |
|---|---|---|
| Line 93 (code fence) | error | from hooks import … — module not importable |