--- summary: "Hooks: event-driven automation for commands and lifecycle events" read_when: - You want event-driven automation for /new, /reset, /stop, and agent lifecycle events - You want to build, install, or debug hooks title: "Hooks" --- # Hooks Hooks provide an extensible event-driven system for automating actions in response to agent commands and events. Hooks are automatically discovered from directories and can be inspected with `openclaw hooks`, while hook-pack installation and updates now go through `openclaw plugins`. ## Getting Oriented Hooks are small scripts that run when something happens. There are two kinds: - **Hooks** (this page): run inside the Gateway when agent events fire, like `/new`, `/reset`, `/stop`, or lifecycle events. - **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhook Hooks](/automation/webhook) or use `openclaw webhooks` for Gmail helper commands. Hooks can also be bundled inside plugins; see [Plugin hooks](/plugins/architecture#provider-runtime-hooks). `openclaw hooks list` shows both standalone hooks and plugin-managed hooks. Common uses: - Save a memory snapshot when you reset a session - Keep an audit trail of commands for troubleshooting or compliance - Trigger follow-up automation when a session starts or ends - Write files into the agent workspace or call external APIs when events fire If you can write a small TypeScript function, you can write a hook. Managed and bundled hooks are trusted local code. Workspace hooks are discovered automatically, but OpenClaw keeps them disabled until you explicitly enable them via the CLI or config. ## Overview The hooks system allows you to: - Save session context to memory when `/new` is issued - Log all commands for auditing - Trigger custom automations on agent lifecycle events - Extend OpenClaw's behavior without modifying core code ## Getting Started ### Bundled Hooks OpenClaw ships with four bundled hooks that are automatically discovered: - **💾 session-memory**: Saves session context to your agent workspace (default `~/.openclaw/workspace/memory/`) when you issue `/new` or `/reset` - **📎 bootstrap-extra-files**: Injects additional workspace bootstrap files from configured glob/path patterns during `agent:bootstrap` - **📝 command-logger**: Logs all command events to `~/.openclaw/logs/commands.log` - **🚀 boot-md**: Runs `BOOT.md` when the gateway starts (requires internal hooks enabled) List available hooks: ```bash openclaw hooks list ``` Enable a hook: ```bash openclaw hooks enable session-memory ``` Check hook status: ```bash openclaw hooks check ``` Get detailed information: ```bash openclaw hooks info session-memory ``` ### Onboarding During onboarding (`openclaw onboard`), you'll be prompted to enable recommended hooks. The wizard automatically discovers eligible hooks and presents them for selection. ### Trust Boundary Hooks run inside the Gateway process. Treat bundled hooks, managed hooks, and `hooks.internal.load.extraDirs` as trusted local code. Workspace hooks under `/hooks/` are repo-local code, so OpenClaw requires an explicit enable step before loading them. ## Hook Discovery Hooks are automatically discovered from these directories, in order of increasing override precedence: 1. **Bundled hooks**: shipped with OpenClaw; located at `/dist/hooks/bundled/` for npm installs (or a sibling `hooks/bundled/` for compiled binaries) 2. **Plugin hooks**: hooks bundled inside installed plugins (see [Plugin hooks](/plugins/architecture#provider-runtime-hooks)) 3. **Managed hooks**: `~/.openclaw/hooks/` (user-installed, shared across workspaces; can override bundled and plugin hooks). **Extra hook directories** configured via `hooks.internal.load.extraDirs` are also treated as managed hooks and share the same override precedence. 4. **Workspace hooks**: `/hooks/` (per-agent, disabled by default until explicitly enabled; cannot override hooks from other sources) Workspace hooks can add new hook names for a repo, but they cannot override bundled, managed, or plugin-provided hooks with the same name. Managed hook directories can be either a **single hook** or a **hook pack** (package directory). Each hook is a directory containing: ``` my-hook/ ├── HOOK.md # Metadata + documentation └── handler.ts # Handler implementation ``` ## Hook Packs (npm/archives) Hook packs are standard npm packages that export one or more hooks via `openclaw.hooks` in `package.json`. Install them with: ```bash openclaw plugins install ``` Npm specs are registry-only (package name + optional exact version or dist-tag). Git/URL/file specs and semver ranges are rejected. Bare specs and `@latest` stay on the stable track. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version. Example `package.json`: ```json { "name": "@acme/my-hooks", "version": "0.1.0", "openclaw": { "hooks": ["./hooks/my-hook", "./hooks/other-hook"] } } ``` Each entry points to a hook directory containing `HOOK.md` and `handler.ts` (or `index.ts`). Hook packs can ship dependencies; they will be installed under `~/.openclaw/hooks/`. Each `openclaw.hooks` entry must stay inside the package directory after symlink resolution; entries that escape are rejected. Security note: `openclaw plugins install` installs hook-pack dependencies with `npm install --ignore-scripts` (no lifecycle scripts). Keep hook pack dependency trees "pure JS/TS" and avoid packages that rely on `postinstall` builds. ## Hook Structure ### HOOK.md Format The `HOOK.md` file contains metadata in YAML frontmatter plus Markdown documentation: ```markdown --- name: my-hook description: "Short description of what this hook does" homepage: https://docs.openclaw.ai/automation/hooks#my-hook metadata: { "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } } --- # My Hook Detailed documentation goes here... ## What It Does - Listens for `/new` commands - Performs some action - Logs the result ## Requirements - Node.js must be installed ## Configuration No configuration needed. ``` ### Metadata Fields The `metadata.openclaw` object supports: - **`emoji`**: Display emoji for CLI (e.g., `"💾"`) - **`events`**: Array of events to listen for (e.g., `["command:new", "command:reset"]`) - **`export`**: Named export to use (defaults to `"default"`) - **`homepage`**: Documentation URL - **`os`**: Required platforms (e.g., `["darwin", "linux"]`) - **`requires`**: Optional requirements - **`bins`**: Required binaries on PATH (e.g., `["git", "node"]`) - **`anyBins`**: At least one of these binaries must be present - **`env`**: Required environment variables - **`config`**: Required config paths (e.g., `["workspace.dir"]`) - **`always`**: Bypass eligibility checks (boolean) - **`install`**: Installation methods (for bundled hooks: `[{"id":"bundled","kind":"bundled"}]`) ### Handler Implementation The `handler.ts` file exports a `HookHandler` function: ```typescript const myHandler = async (event) => { // Only trigger on 'new' command if (event.type !== "command" || event.action !== "new") { return; } console.log(`[my-hook] New command triggered`); console.log(` Session: ${event.sessionKey}`); console.log(` Timestamp: ${event.timestamp.toISOString()}`); // Your custom logic here // Optionally send message to user event.messages.push("✨ My hook executed!"); }; export default myHandler; ``` #### Event Context Each event includes: ```typescript { type: 'command' | 'session' | 'agent' | 'gateway' | 'message', action: string, // e.g., 'new', 'reset', 'stop', 'received', 'sent' sessionKey: string, // Session identifier timestamp: Date, // When the event occurred messages: string[], // Push messages here to send to user context: { // Command events (command:new, command:reset): sessionEntry?: SessionEntry, // current session entry previousSessionEntry?: SessionEntry, // pre-reset entry (preferred for session-memory) commandSource?: string, // e.g., 'whatsapp', 'telegram' senderId?: string, workspaceDir?: string, cfg?: OpenClawConfig, // Command events (command:stop only): sessionId?: string, // Agent bootstrap events (agent:bootstrap): bootstrapFiles?: WorkspaceBootstrapFile[], // Message events (see Message Events section for full details): from?: string, // message:received to?: string, // message:sent content?: string, channelId?: string, success?: boolean, // message:sent } } ``` ## Event Types ### Command Events Triggered when agent commands are issued: - **`command`**: All command events (general listener) - **`command:new`**: When `/new` command is issued - **`command:reset`**: When `/reset` command is issued - **`command:stop`**: When `/stop` command is issued ### Session Events - **`session:compact:before`**: Right before compaction summarizes history - **`session:compact:after`**: After compaction completes with summary metadata Internal hook payloads emit these as `type: "session"` with `action: "compact:before"` / `action: "compact:after"`; listeners subscribe with the combined keys above. Specific handler registration uses the literal key format `${type}:${action}`. For these events, register `session:compact:before` and `session:compact:after`. ### Agent Events - **`agent:bootstrap`**: Before workspace bootstrap files are injected (hooks may mutate `context.bootstrapFiles`) ### Gateway Events Triggered when the gateway starts: - **`gateway:startup`**: After channels start and hooks are loaded ### Message Events Triggered when messages are received or sent: - **`message`**: All message events (general listener) - **`message:received`**: When an inbound message is received from any channel. Fires early in processing before media understanding. Content may contain raw placeholders like `` for media attachments that haven't been processed yet. - **`message:transcribed`**: When a message has been fully processed, including audio transcription and link understanding. At this point, `transcript` contains the full transcript text for audio messages. Use this hook when you need access to transcribed audio content. - **`message:preprocessed`**: Fires for every message after all media + link understanding completes, giving hooks access to the fully enriched body (transcripts, image descriptions, link summaries) before the agent sees it. - **`message:sent`**: When an outbound message is successfully sent #### Message Event Context Message events include rich context about the message: ```typescript // message:received context { from: string, // Sender identifier (phone number, user ID, etc.) content: string, // Message content timestamp?: number, // Unix timestamp when received channelId: string, // Channel (e.g., "whatsapp", "telegram", "discord") accountId?: string, // Provider account ID for multi-account setups conversationId?: string, // Chat/conversation ID messageId?: string, // Message ID from the provider metadata?: { // Additional provider-specific data to?: string, provider?: string, surface?: string, threadId?: string | number, senderId?: string, senderName?: string, senderUsername?: string, senderE164?: string, guildId?: string, // Discord guild / server ID channelName?: string, // Channel name (e.g., Discord channel name) } } // message:sent context { to: string, // Recipient identifier content: string, // Message content that was sent success: boolean, // Whether the send succeeded error?: string, // Error message if sending failed channelId: string, // Channel (e.g., "whatsapp", "telegram", "discord") accountId?: string, // Provider account ID conversationId?: string, // Chat/conversation ID messageId?: string, // Message ID returned by the provider isGroup?: boolean, // Whether this outbound message belongs to a group/channel context groupId?: string, // Group/channel identifier for correlation with message:received } // message:transcribed context { from?: string, // Sender identifier to?: string, // Recipient identifier body?: string, // Raw inbound body before enrichment bodyForAgent?: string, // Enriched body visible to the agent transcript: string, // Audio transcript text timestamp?: number, // Unix timestamp when received channelId: string, // Channel (e.g., "telegram", "whatsapp") conversationId?: string, messageId?: string, senderId?: string, // Sender user ID senderName?: string, // Sender display name senderUsername?: string, provider?: string, // Provider name surface?: string, // Surface name mediaPath?: string, // Path to the media file that was transcribed mediaType?: string, // MIME type of the media } // message:preprocessed context { from?: string, // Sender identifier to?: string, // Recipient identifier body?: string, // Raw inbound body bodyForAgent?: string, // Final enriched body after media/link understanding transcript?: string, // Transcript when audio was present timestamp?: number, // Unix timestamp when received channelId: string, // Channel (e.g., "telegram", "whatsapp") conversationId?: string, messageId?: string, senderId?: string, // Sender user ID senderName?: string, // Sender display name senderUsername?: string, provider?: string, // Provider name surface?: string, // Surface name mediaPath?: string, // Path to the media file mediaType?: string, // MIME type of the media isGroup?: boolean, groupId?: string, } ``` #### Example: Message Logger Hook ```typescript const isMessageReceivedEvent = (event: { type: string; action: string }) => event.type === "message" && event.action === "received"; const isMessageSentEvent = (event: { type: string; action: string }) => event.type === "message" && event.action === "sent"; const handler = async (event) => { if (isMessageReceivedEvent(event as { type: string; action: string })) { console.log(`[message-logger] Received from ${event.context.from}: ${event.context.content}`); } else if (isMessageSentEvent(event as { type: string; action: string })) { console.log(`[message-logger] Sent to ${event.context.to}: ${event.context.content}`); } }; export default handler; ``` ### Tool Result Hooks (Plugin API) These hooks are not event-stream listeners; they let plugins synchronously adjust tool results before OpenClaw persists them. - **`tool_result_persist`**: transform tool results before they are written to the session transcript. Must be synchronous; return the updated tool result payload or `undefined` to keep it as-is. See [Agent Loop](/concepts/agent-loop). ### Plugin Hook Events Compaction lifecycle hooks exposed through the plugin hook runner: - **`before_compaction`**: Runs before compaction with count/token metadata - **`after_compaction`**: Runs after compaction with compaction summary metadata ### Future Events Planned event types: - **`session:start`**: When a new session begins - **`session:end`**: When a session ends - **`agent:error`**: When an agent encounters an error ## Creating Custom Hooks ### 1. Choose Location - **Workspace hooks** (`/hooks/`): Per-agent; can add new hook names but cannot override bundled, managed, or plugin hooks with the same name - **Managed hooks** (`~/.openclaw/hooks/`): Shared across workspaces; can override bundled and plugin hooks ### 2. Create Directory Structure ```bash mkdir -p ~/.openclaw/hooks/my-hook cd ~/.openclaw/hooks/my-hook ``` ### 3. Create HOOK.md ```markdown --- name: my-hook description: "Does something useful" metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } } --- # My Custom Hook This hook does something useful when you issue `/new`. ``` ### 4. Create handler.ts ```typescript const handler = async (event) => { if (event.type !== "command" || event.action !== "new") { return; } console.log("[my-hook] Running!"); // Your logic here }; export default handler; ``` ### 5. Enable and Test ```bash # Verify hook is discovered openclaw hooks list # Enable it openclaw hooks enable my-hook # Restart your gateway process (menu bar app restart on macOS, or restart your dev process) # Trigger the event # Send /new via your messaging channel ``` ## Configuration ### New Config Format (Recommended) ```json { "hooks": { "internal": { "enabled": true, "entries": { "session-memory": { "enabled": true }, "command-logger": { "enabled": false } } } } } ``` ### Per-Hook Configuration Hooks can have custom configuration: ```json { "hooks": { "internal": { "enabled": true, "entries": { "my-hook": { "enabled": true, "env": { "MY_CUSTOM_VAR": "value" } } } } } } ``` ### Extra Directories Load hooks from additional directories (treated as managed hooks, same override precedence): ```json { "hooks": { "internal": { "enabled": true, "load": { "extraDirs": ["/path/to/more/hooks"] } } } } ``` ### Legacy Config Format (Still Supported) The old config format still works for backwards compatibility: ```json { "hooks": { "internal": { "enabled": true, "handlers": [ { "event": "command:new", "module": "./hooks/handlers/my-handler.ts", "export": "default" } ] } } } ``` Note: `module` must be a workspace-relative path. Absolute paths and traversal outside the workspace are rejected. **Migration**: Use the new discovery-based system for new hooks. Legacy handlers are loaded after directory-based hooks. ## CLI Commands ### List Hooks ```bash # List all hooks openclaw hooks list # Show only eligible hooks openclaw hooks list --eligible # Verbose output (show missing requirements) openclaw hooks list --verbose # JSON output openclaw hooks list --json ``` ### Hook Information ```bash # Show detailed info about a hook openclaw hooks info session-memory # JSON output openclaw hooks info session-memory --json ``` ### Check Eligibility ```bash # Show eligibility summary openclaw hooks check # JSON output openclaw hooks check --json ``` ### Enable/Disable ```bash # Enable a hook openclaw hooks enable session-memory # Disable a hook openclaw hooks disable command-logger ``` ## Bundled hook reference ### session-memory Saves session context to memory when you issue `/new` or `/reset`. **Events**: `command:new`, `command:reset` **Requirements**: `workspace.dir` must be configured **Output**: `/memory/YYYY-MM-DD-slug.md` (defaults to `~/.openclaw/workspace`) **What it does**: 1. Uses the pre-reset session entry to locate the correct transcript 2. Extracts the last 15 user/assistant messages from the conversation (configurable) 3. Uses LLM to generate a descriptive filename slug 4. Saves session metadata to a dated memory file **Example output**: ```markdown # Session: 2026-01-16 14:30:00 UTC - **Session Key**: agent:main:main - **Session ID**: abc123def456 - **Source**: telegram ## Conversation Summary user: Can you help me design the API? assistant: Sure! Let's start with the endpoints... ``` **Filename examples**: - `2026-01-16-vendor-pitch.md` - `2026-01-16-api-design.md` - `2026-01-16-1430.md` (fallback timestamp if slug generation fails) **Enable**: ```bash openclaw hooks enable session-memory ``` ### bootstrap-extra-files Injects additional bootstrap files (for example monorepo-local `AGENTS.md` / `TOOLS.md`) during `agent:bootstrap`. **Events**: `agent:bootstrap` **Requirements**: `workspace.dir` must be configured **Output**: No files written; bootstrap context is modified in-memory only. **Config**: ```json { "hooks": { "internal": { "enabled": true, "entries": { "bootstrap-extra-files": { "enabled": true, "paths": ["packages/*/AGENTS.md", "packages/*/TOOLS.md"] } } } } } ``` **Config options**: - `paths` (string[]): glob/path patterns to resolve from the workspace. - `patterns` (string[]): alias of `paths`. - `files` (string[]): alias of `paths`. **Notes**: - Paths are resolved relative to workspace. - Files must stay inside workspace (realpath-checked). - Only recognized bootstrap basenames are loaded (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md`, `memory.md`). - For subagent/cron sessions a narrower allowlist applies (`AGENTS.md`, `TOOLS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`). **Enable**: ```bash openclaw hooks enable bootstrap-extra-files ``` ### command-logger Logs all command events to a centralized audit file. **Events**: `command` **Requirements**: None **Output**: `~/.openclaw/logs/commands.log` **What it does**: 1. Captures event details (command action, timestamp, session key, sender ID, source) 2. Appends to log file in JSONL format 3. Runs silently in the background **Example log entries**: ```jsonl {"timestamp":"2026-01-16T14:30:00.000Z","action":"new","sessionKey":"agent:main:main","senderId":"+1234567890","source":"telegram"} {"timestamp":"2026-01-16T15:45:22.000Z","action":"stop","sessionKey":"agent:main:main","senderId":"user@example.com","source":"whatsapp"} ``` **View logs**: ```bash # View recent commands tail -n 20 ~/.openclaw/logs/commands.log # Pretty-print with jq cat ~/.openclaw/logs/commands.log | jq . # Filter by action grep '"action":"new"' ~/.openclaw/logs/commands.log | jq . ``` **Enable**: ```bash openclaw hooks enable command-logger ``` ### boot-md Runs `BOOT.md` when the gateway starts (after channels start). Internal hooks must be enabled for this to run. **Events**: `gateway:startup` **Requirements**: `workspace.dir` must be configured **What it does**: 1. Reads `BOOT.md` from your workspace 2. Runs the instructions via the agent runner 3. Sends any requested outbound messages via the message tool **Enable**: ```bash openclaw hooks enable boot-md ``` ## Best Practices ### Keep Handlers Fast Hooks run during command processing. Keep them lightweight: ```typescript // ✓ Good - async work, returns immediately const handler: HookHandler = async (event) => { void processInBackground(event); // Fire and forget }; // ✗ Bad - blocks command processing const handler: HookHandler = async (event) => { await slowDatabaseQuery(event); await evenSlowerAPICall(event); }; ``` ### Handle Errors Gracefully Always wrap risky operations: ```typescript const handler: HookHandler = async (event) => { try { await riskyOperation(event); } catch (err) { console.error("[my-handler] Failed:", err instanceof Error ? err.message : String(err)); // Don't throw - let other handlers run } }; ``` ### Filter Events Early Return early if the event isn't relevant: ```typescript const handler: HookHandler = async (event) => { // Only handle 'new' commands if (event.type !== "command" || event.action !== "new") { return; } // Your logic here }; ``` ### Use Specific Event Keys Specify exact events in metadata when possible: ```yaml metadata: { "openclaw": { "events": ["command:new"] } } # Specific ``` Rather than: ```yaml metadata: { "openclaw": { "events": ["command"] } } # General - more overhead ``` ## Debugging ### Enable Hook Logging The gateway logs hook loading at startup: ``` Registered hook: session-memory -> command:new Registered hook: bootstrap-extra-files -> agent:bootstrap Registered hook: command-logger -> command Registered hook: boot-md -> gateway:startup ``` ### Check Discovery List all discovered hooks: ```bash openclaw hooks list --verbose ``` ### Check Registration In your handler, log when it's called: ```typescript const handler: HookHandler = async (event) => { console.log("[my-handler] Triggered:", event.type, event.action); // Your logic }; ``` ### Verify Eligibility Check why a hook isn't eligible: ```bash openclaw hooks info my-hook ``` Look for missing requirements in the output. ## Testing ### Gateway Logs Monitor gateway logs to see hook execution: ```bash # macOS ./scripts/clawlog.sh -f # Other platforms tail -f ~/.openclaw/gateway.log ``` ### Test Hooks Directly Test your handlers in isolation: ```typescript import { test } from "vitest"; import myHandler from "./hooks/my-hook/handler.js"; test("my handler works", async () => { const event = { type: "command", action: "new", sessionKey: "test-session", timestamp: new Date(), messages: [], context: { foo: "bar" }, }; await myHandler(event); // Assert side effects }); ``` ## Architecture ### Core Components - **`src/hooks/types.ts`**: Type definitions - **`src/hooks/workspace.ts`**: Directory scanning and loading - **`src/hooks/frontmatter.ts`**: HOOK.md metadata parsing - **`src/hooks/config.ts`**: Eligibility checking - **`src/hooks/hooks-status.ts`**: Status reporting - **`src/hooks/loader.ts`**: Dynamic module loader - **`src/cli/hooks-cli.ts`**: CLI commands - **`src/gateway/server-startup.ts`**: Loads hooks at gateway start - **`src/auto-reply/reply/commands-core.ts`**: Triggers command events ### Discovery Flow ``` Gateway startup ↓ Scan directories (bundled → plugin → managed + extra dirs → workspace) ↓ Parse HOOK.md files ↓ Sort by override precedence (bundled < plugin < managed < workspace) ↓ Check eligibility (bins, env, config, os) ↓ Load handlers from eligible hooks ↓ Register handlers for events ``` ### Event Flow ``` User sends /new ↓ Command validation ↓ Create hook event ↓ Trigger hook (all registered handlers) ↓ Command processing continues ↓ Session reset ``` ## Troubleshooting ### Hook Not Discovered 1. Check directory structure: ```bash ls -la ~/.openclaw/hooks/my-hook/ # Should show: HOOK.md, handler.ts ``` 2. Verify HOOK.md format: ```bash cat ~/.openclaw/hooks/my-hook/HOOK.md # Should have YAML frontmatter with name and metadata ``` 3. List all discovered hooks: ```bash openclaw hooks list ``` ### Hook Not Eligible Check requirements: ```bash openclaw hooks info my-hook ``` Look for missing: - Binaries (check PATH) - Environment variables - Config values - OS compatibility ### Hook Not Executing 1. Verify hook is enabled: ```bash openclaw hooks list # Should show ✓ next to enabled hooks ``` 2. Restart your gateway process so hooks reload. 3. Check gateway logs for errors: ```bash ./scripts/clawlog.sh | grep hook ``` ### Handler Errors Check for TypeScript/import errors: ```bash # Test import directly node -e "import('./path/to/handler.ts').then(console.log)" ``` ## Migration Guide ### From Legacy Config to Discovery **Before**: ```json { "hooks": { "internal": { "enabled": true, "handlers": [ { "event": "command:new", "module": "./hooks/handlers/my-handler.ts" } ] } } } ``` **After**: 1. Create hook directory: ```bash mkdir -p ~/.openclaw/hooks/my-hook mv ./hooks/handlers/my-handler.ts ~/.openclaw/hooks/my-hook/handler.ts ``` 2. Create HOOK.md: ```markdown --- name: my-hook description: "My custom hook" metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } } --- # My Hook Does something useful. ``` 3. Update config: ```json { "hooks": { "internal": { "enabled": true, "entries": { "my-hook": { "enabled": true } } } } } ``` 4. Verify and restart your gateway process: ```bash openclaw hooks list # Should show: 🎯 my-hook ✓ ``` **Benefits of migration**: - Automatic discovery - CLI management - Eligibility checking - Better documentation - Consistent structure ## See Also - [CLI Reference: hooks](/cli/hooks) - [Bundled Hooks README](https://github.com/openclaw/openclaw/tree/main/src/hooks/bundled) - [Webhook Hooks](/automation/webhook) - [Configuration](/gateway/configuration-reference#hooks)