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

Steps

  1. Create a HookRegistry. Instantiate HookRegistry from hooks. If you already have a HookConfig loaded 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)
    
  2. Register a handler for an event. Call registry.register() with the target HookEvent, your callable handler, and an optional HookMatcher if the hook should fire only when the context meets specific conditions. The method returns a handler_id string — 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,
    )
    
  3. Fire the event. Call registry.fire() to dispatch the event asynchronously, or registry.fire_sync() when you need results in a synchronous context. Pass a context dict with any data your handlers require.

    results = registry.fire_sync(
        HookEvent.POST_TOOL,
        context={"tool": "bash", "output": "..."},
    )
    
  4. Inspect results and the execution log. fire and fire_sync both return a list of dict[str, Any] — one entry per handler that ran. To review the full history of dispatched events, call registry.get_execution_log(). Filter by event type with the event_filter parameter, or limit the number of entries with limit.

    log = registry.get_execution_log(limit=50, event_filter=HookEvent.POST_TOOL)
    stats = registry.get_stats()
    
  5. Remove a handler when it is no longer needed. Call registry.unregister() with the handler_id returned in step 2. The method returns True if the handler was found and removed.

    removed = registry.unregister(handler_id)
    
  6. 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:

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:

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