Event Hooks
Event hooks let you react to things happening in your kiln - tool calls, note changes, server connections. Write a Lua function, add an annotation, and Crucible calls it automatically.
Basic Example
Section titled “Basic Example”--- Log every tool call-- @handler event="tool:after" pattern="*" priority=100function log_tools(ctx, event) cru.log("info", "Tool called: " .. event.identifier) return eventendPlace this in a .lua file in your plugins/ folder and it runs whenever a tool executes.
The Handler Annotation
Section titled “The Handler Annotation”Every hook needs the @handler annotation:
--- My hook description-- @handler event="tool:after" pattern="gh_*" priority=50function my_hook(ctx, event) -- Process event return event -- Always return the eventendParameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
event | Yes | - | Event type to handle |
pattern | No | "*" | Glob pattern for filtering |
priority | No | 100 | Lower runs first |
Event Types
Section titled “Event Types”Tool Events:
tool:before- Before tool runs (can cancel)tool:after- After tool completestool:error- Tool failedtool:discovered- New tool registered
Note Events:
note:parsed- Note was parsednote:created- New note creatednote:modified- Note changed
Server Events:
mcp:attached- External MCP server connected
Pattern Matching
Section titled “Pattern Matching”Patterns use glob syntax:
-- @handler event="tool:after" pattern="*" -- All tools-- @handler event="tool:after" pattern="gh_*" -- GitHub tools-- @handler event="tool:after" pattern="just_test*" -- Just test recipesPractical Examples
Section titled “Practical Examples”Filter Verbose Output
Section titled “Filter Verbose Output”--- Keep only summary from test output-- @handler event="tool:after" pattern="just_test*" priority=10function filter_test_output(ctx, event) local result = event.payload.result
if result and result.content then local text = result.content[1].text local filtered = keep_summary_lines(text) result.content[1].text = filtered end
return eventendBlock Dangerous Operations
Section titled “Block Dangerous Operations”--- Prevent accidental deletions-- @handler event="tool:before" pattern="*delete*" priority=5function block_deletes(ctx, event) cru.log("warn", "Blocked: " .. event.identifier) event.cancelled = true return eventendAdd Metadata to Tools
Section titled “Add Metadata to Tools”--- Tag tools by category-- @handler event="tool:discovered" pattern="just_*" priority=5function categorize_recipes(ctx, event) local name = event.identifier
if string.find(name, "test") then event.payload.category = "testing" elseif string.find(name, "build") then event.payload.category = "build" end
return eventendThe Event Object
Section titled “The Event Object”Hooks receive an event with these fields:
event.event_type -- "tool:after", "note:parsed", etc.event.identifier -- Tool name, note path, etc.event.payload -- Event-specific dataevent.timestamp_ms -- When it happenedevent.cancelled -- Set true to cancel (tool:before only)The Context Object
Section titled “The Context Object”Use ctx to store data and emit new events:
ctx:set("key", value) -- Store datactx:get("key") -- Retrieve datactx:emit("my:event", { -- Emit custom event data = "value"})Priority Guide
Section titled “Priority Guide”Lower numbers run earlier:
| Range | Use |
|---|---|
| 0-9 | Security/validation |
| 10-49 | Early processing |
| 50-99 | Transformation |
| 100-149 | General (default) |
| 150-199 | Cleanup |
| 200+ | Logging/audit |
Common Patterns
Section titled “Common Patterns”Multi-Stage Processing
Section titled “Multi-Stage Processing”--- Stage 1: Extract data-- @handler event="note:parsed" pattern="*" priority=10function extract(ctx, event) ctx:set("tags", event.payload.tags) return eventend
--- Stage 2: Use extracted data-- @handler event="note:parsed" pattern="*" priority=20function process(ctx, event) local tags = ctx:get("tags") if tags then -- Process tags end return eventendConditional Processing
Section titled “Conditional Processing”-- @handler event="tool:after" pattern="*" priority=100function conditional(ctx, event) if should_process(event) then -- Do something end return eventendBest Practices
Section titled “Best Practices”- Always return the event - even if unchanged
- Keep hooks fast - avoid blocking operations
- Use specific patterns - reduces unnecessary invocations
- Handle errors gracefully - check before accessing fields
- Add doc comments - explain what the hook does
See Also
Section titled “See Also”- Custom Handlers - Advanced handler development
- MCP Gateway - External tool integration
- Language Basics - Lua syntax
- Extending Crucible - All extension points