Skip to main content
xbot
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Hooks allow plugins to intercept and modify xbot’s behavior at lifecycle points — before/after tool execution, on user prompts, session events, and errors.

Hook Events

EventMethodDescription
PreToolUseOnPreToolUse(matcher, handler)Before a tool executes. Can deny, defer, or modify
PostToolUseOnPostToolUse(matcher, handler)After a tool executes. Can modify results
UserPromptOnUserPrompt(handler)When user sends a message
AgentStopOnAgentStop(handler)When the agent stops
SessionStartOnSessionStart(handler)When a session starts
SessionEndOnSessionEnd(handler)When a session ends
AllToolUseOnAllToolUse(handler)Before any tool (no matcher)
OnErrorOnError(handler)When an error occurs

HookHandler

type HookHandler func(ctx context.Context, payload *HookPayload) (*HookResult, error)

HookPayload

type HookPayload struct {
    Event       HookEvent    // The event type
    ToolName    string       // Tool name (for tool hooks)
    ToolInput   string       // Tool input (for PreToolUse)
    ToolOutput  string       // Tool output (for PostToolUse, truncated to 8KB)
    SessionID   string       // Session identifier
    WorkDir     string       // Working directory
    Channel     string       // Channel name (cli, web, feishu)
    Error       error        // Error (for OnError)
    Extra       map[string]any // Extra context (model, token usage, etc.)
}

HookResult

type HookResult struct {
    Decision HookDecision  // allow, deny, defer, ask
    Message  string        // Message shown to user (for deny/ask)
    Data     any           // Modified data (for PostToolUse)
}

Decisions

DecisionDescription
DecisionAllowAllow the action to proceed
DecisionDenyBlock the action, show message to user
DecisionDeferDefer to the next hook in the chain
DecisionAskAsk the user for confirmation

Decision priority: deny > defer > ask > allow. A low-priority layer’s deny cannot be overridden by a high-priority allow.

Matcher

The matcher parameter for tool hooks is a tool name pattern:

// Match all tools
ctx.OnPreToolUse("", handler)

// Match specific tool
ctx.OnPreToolUse("Shell", handler)

// Match tools by prefix
ctx.OnPreToolUse("Read", handler)  // Matches "Read"

Usage Examples

Deny Shell commands containing “rm”

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    return ctx.OnPreToolUse("Shell", func(ctx context.Context, payload *plugin.HookPayload) (*plugin.HookResult, error) {
        if strings.Contains(payload.ToolInput, "rm ") {
            return &plugin.HookResult{
                Decision: plugin.DecisionDeny,
                Message:  "rm commands are blocked by safety policy",
            }, nil
        }
        return &plugin.HookResult{Decision: plugin.DecisionAllow}, nil
    })
}

Log all tool usage

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    return ctx.OnPostToolUse("", func(ctx context.Context, payload *plugin.HookPayload) (*plugin.HookResult, error) {
        logger := ctx.Logger()
        logger.Info("Tool executed",
            plugin.Field{Key: "tool", Value: payload.ToolName},
            plugin.Field{Key: "workDir", Value: payload.WorkDir},
        )
        return &plugin.HookResult{Decision: plugin.DecisionAllow}, nil
    })
}

Session lifecycle tracking

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    ctx.OnSessionStart(func(ctx context.Context, payload *plugin.HookPayload) (*plugin.HookResult, error) {
        ctx.Logger().Info("Session started", plugin.Field{Key: "workDir", Value: payload.WorkDir})
        return &plugin.HookResult{Decision: plugin.DecisionAllow}, nil
    })
    
    ctx.OnSessionEnd(func(ctx context.Context, payload *plugin.HookPayload) (*plugin.HookResult, error) {
        ctx.Logger().Info("Session ended")
        return &plugin.HookResult{Decision: plugin.DecisionAllow}, nil
    })
    
    return nil
}

SDK Helpers

// Pre-built hook handlers
plugin.DenyHook("blocked")    // Always deny with message
plugin.AllowHook()             // Always allow
plugin.LogHook(logger, "msg") // Log and allow

Manifest Declaration

Hooks can also be declared in plugin.json (for documentation purposes):

{
  "contributes": {
    "hooks": [
      {"event": "PreToolUse", "matcher": "Shell"},
      {"event": "PostToolUse", "matcher": ""}
    ]
  }
}

Script Plugin Hooks

Script plugins receive hooks via environment variables:

#!/bin/bash
# Access hook data via environment variables
echo "Tool: $XBOT_HOOK_TOOL_NAME"
echo "Input: $XBOT_HOOK_TOOL_INPUT"

See Also