Hooks
Hooks are small scripts that run when something happens inside the Gateway. They can be discovered from directories and inspected with openclaw hooks. The Gateway loads internal hooks only after you enable hooks or configure at least one hook entry, hook pack, legacy handler, or extra hook directory.
There are two kinds of hooks in OpenClaw:
- Internal hooks (this page): run inside the Gateway when agent events fire, like
/new,/reset,/stop, or lifecycle events. - Webhooks: external HTTP endpoints that let other systems trigger work in OpenClaw. See Webhooks.
Hooks can also be bundled inside plugins. openclaw hooks list shows both standalone hooks and plugin-managed hooks.
Choose the right surface
Section titled “Choose the right surface”OpenClaw has several extension surfaces that look similar but solve different problems:
| If you want to… | Use… | Why |
|---|---|---|
Save a snapshot on /new, log /reset, call an external API after message:sent, or add coarse operator automation | Internal hooks (HOOK.md, this page) | File-based hooks are meant for operator-managed side effects and command/lifecycle automation |
| Rewrite prompts, block tools, cancel outbound messages, or add ordered middleware/policy | Typed plugin hooks via api.on(...) | Typed hooks have explicit contracts, priorities, merge rules, and block/cancel semantics |
| Add telemetry-only export or observability | Diagnostic events | Observability is a separate event bus, not a policy hook surface |
Use internal hooks when you want automation that behaves like a small installed integration. Use typed plugin hooks when you need runtime lifecycle control.
Quick start
Section titled “Quick start”# List available hooksopenclaw hooks list
# Enable a hookopenclaw hooks enable session-memory
# Check hook statusopenclaw hooks check
# Get detailed informationopenclaw hooks info session-memoryEvent types
Section titled “Event types”| Event | When it fires |
|---|---|
command:new | /new command issued |
command:reset | /reset command issued |
command:stop | /stop command issued |
command | Any command event (general listener) |
session:compact:before | Before compaction summarizes history |
session:compact:after | After compaction completes |
session:patch | When session properties are modified |
agent:bootstrap | Before workspace bootstrap files are injected |
gateway:startup | After channels start and hooks are loaded |
gateway:shutdown | When gateway shutdown begins |
gateway:pre-restart | Before an expected gateway restart |
message:received | Inbound message from any channel |
message:transcribed | After audio transcription completes |
message:preprocessed | After media and link preprocessing completes or is skipped |
message:sent | Outbound message delivered |
Writing hooks
Section titled “Writing hooks”Hook structure
Section titled “Hook structure”Each hook is a directory containing two files:
my-hook/├── HOOK.md # Metadata + documentation└── handler.ts # Handler implementationHOOK.md format
Section titled “HOOK.md format”---name: my-hookdescription: "Short description of what this hook does"metadata: { "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } }---
# My Hook
Detailed documentation goes here.Metadata fields (metadata.openclaw):
| Field | Description |
|---|---|
emoji | Display emoji for CLI |
events | Array of events to listen for |
export | Named export to use (defaults to "default") |
os | Required platforms (e.g., ["darwin", "linux"]) |
requires | Required bins, anyBins, env, or config paths |
always | Bypass eligibility checks (boolean) |
install | Installation methods |
Handler implementation
Section titled “Handler implementation”const handler = async (event) => { if (event.type !== "command" || event.action !== "new") { return; }
console.log(`[my-hook] New command triggered`); // Your logic here
// Optionally send a reply on replyable surfaces event.messages.push("Hook executed!");};
export default handler;Each event includes: type, action, sessionKey, timestamp, messages (push replies here on replyable surfaces only), and context (event-specific data). Agent and tool plugin hook contexts can also include trace, a read-only W3C-compatible diagnostic trace context that plugins may pass into structured logs for OTEL correlation.
event.messages is only delivered automatically on replyable surfaces such as
command:* and message:received. Lifecycle-only events such as
agent:bootstrap, session:*, gateway:*, or message:sent do not have a
reply channel and ignore pushed messages.
Event context highlights
Section titled “Event context highlights”Command events (command:new, command:reset): context.sessionEntry, context.previousSessionEntry, context.commandSource, context.workspaceDir, context.cfg.
Message events (message:received): context.from, context.content, context.channelId, context.metadata (provider-specific data including senderId, senderName, guildId). context.content prefers a nonblank command body for command-like messages, then falls back to the raw inbound body and generic body; it does not include agent-only enrichment such as thread history or link summaries.
Message events (message:sent): context.to, context.content, context.success, context.channelId.
Message events (message:transcribed): context.transcript, context.from, context.channelId, context.mediaPath.
Message events (message:preprocessed): context.bodyForAgent (final enriched body), context.from, context.channelId.
Bootstrap events (agent:bootstrap): context.bootstrapFiles (mutable array), context.agentId.
Session patch events (session:patch): context.sessionEntry, context.patch (only changed fields), context.cfg. Only privileged clients can trigger patch events.
Compaction events: session:compact:before includes messageCount, tokenCount. session:compact:after adds compactedCount, summaryLength, tokensBefore, tokensAfter.
command:stop observes the user issuing /stop; it is cancellation/command
lifecycle, not an agent-finalization gate. Plugins that need to inspect a
natural final answer and ask the agent for one more pass should use the typed
plugin hook before_agent_finalize instead. See Plugin hooks.
Gateway lifecycle events: gateway:shutdown includes reason and restartExpectedMs and fires when gateway shutdown begins. gateway:pre-restart includes the same context but only fires when shutdown is part of an expected restart and a finite restartExpectedMs value is supplied. During shutdown, each lifecycle hook wait is best-effort and bounded so shutdown continues if a handler stalls. The default wait budget is 5 seconds for gateway:shutdown and 10 seconds for gateway:pre-restart.
Use gateway:pre-restart for short restart notices while channels are still available:
import { execFile } from "node:child_process";import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
export default async function handler(event) { if (event.type !== "gateway" || event.action !== "pre-restart") { return; }
const restartInSeconds = Math.ceil(event.context.restartExpectedMs / 1000); await execFileAsync("openclaw", [ "system", "event", "--mode", "now", "--text", `Gateway restarting in ~${restartInSeconds}s (${event.context.reason}). Checkpoint now.`, ]);}Between the gateway:shutdown (or gateway:pre-restart) event and the rest of the shutdown sequence, the gateway also fires a typed session_end plugin hook for every session that was still active when the process stopped. The event’s reason is shutdown for a plain SIGTERM/SIGINT stop and restart when the close was scheduled as part of an expected restart. This drain is bounded so a slow session_end handler cannot block process exit, and sessions that have already been finalized through replace / reset / delete / compaction are skipped to avoid double-firing.
Hook discovery
Section titled “Hook discovery”Hooks are discovered from these directories, in order of increasing override precedence:
- Bundled hooks: shipped with OpenClaw
- Plugin hooks: hooks bundled inside installed plugins
- Managed hooks:
~/.openclaw/hooks/(user-installed, shared across workspaces). Extra directories fromhooks.internal.load.extraDirsshare this precedence. - Workspace hooks:
<workspace>/hooks/(per-agent, disabled by default until explicitly enabled)
Workspace hooks can add new hook names but cannot override bundled, managed, or plugin-provided hooks with the same name.
The Gateway skips internal hook discovery on startup until internal hooks are configured. Enable a bundled or managed hook with openclaw hooks enable <name>, install a hook pack, or set hooks.internal.enabled=true to opt in. When you enable one named hook, the Gateway loads only that hook’s handler; hooks.internal.enabled=true, extra hook directories, and legacy handlers opt into broad discovery.
Hook packs
Section titled “Hook packs”Hook packs are npm packages that export hooks via openclaw.hooks in package.json. Install with:
openclaw plugins install <path-or-spec>Npm specs are registry-only (package name + optional exact version or dist-tag). Git/URL/file specs and semver ranges are rejected.
Bundled hooks
Section titled “Bundled hooks”| Hook | Events | What it does |
|---|---|---|
| session-memory | command:new, command:reset | Saves session context to <workspace>/memory/ |
| bootstrap-extra-files | agent:bootstrap | Injects additional bootstrap files from glob patterns |
| command-logger | command | Logs all commands to ~/.openclaw/logs/commands.log |
| compaction-notifier | session:compact:before, session:compact:after | Sends visible chat notices when session compaction starts/ends |
| boot-md | gateway:startup | Runs BOOT.md when the gateway starts |
Enable any bundled hook:
openclaw hooks enable <hook-name>session-memory details
Section titled “session-memory details”Extracts the last 15 user/assistant messages and saves to <workspace>/memory/YYYY-MM-DD-HHMM.md using the host local date. Memory capture runs in the background so /new and /reset acknowledgements are not delayed by transcript reads or optional slug generation. Set hooks.internal.entries.session-memory.llmSlug: true to generate descriptive filename slugs with the configured model. Requires workspace.dir to be configured.
bootstrap-extra-files config
Section titled “bootstrap-extra-files config”{ "hooks": { "internal": { "entries": { "bootstrap-extra-files": { "enabled": true, "paths": ["packages/*/AGENTS.md", "packages/*/TOOLS.md"] } } } }}Paths resolve relative to workspace. Only recognized bootstrap basenames are loaded (AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md, HEARTBEAT.md, BOOTSTRAP.md, MEMORY.md).
command-logger details
Section titled “command-logger details”Logs every slash command to ~/.openclaw/logs/commands.log.
compaction-notifier details
Section titled “compaction-notifier details”Sends short status messages into the current conversation when OpenClaw starts and finishes compacting the session transcript. This makes long turns less confusing on chat surfaces because the user can see that the assistant is summarizing context and will continue after compaction.
boot-md details
Section titled “boot-md details”Runs BOOT.md from the active workspace when the gateway starts.
Plugin hooks
Section titled “Plugin hooks”Plugins can register typed hooks through the Plugin SDK for deeper integration:
intercepting tool calls, modifying prompts, controlling message flow, and more.
Use plugin hooks when you need before_tool_call, before_agent_reply,
before_install, or other in-process lifecycle hooks.
Plugin-managed internal hooks are different: they participate in this page’s
coarse command/lifecycle event system and show up in openclaw hooks list as
plugin:<id>. Use those for side effects and compatibility with hook packs, not
for ordered middleware or policy gates.
For the complete plugin hook reference, see Plugin hooks.
Configuration
Section titled “Configuration”{ "hooks": { "internal": { "enabled": true, "entries": { "session-memory": { "enabled": true }, "command-logger": { "enabled": false } } } }}Per-hook environment variables:
{ "hooks": { "internal": { "entries": { "my-hook": { "enabled": true, "env": { "MY_CUSTOM_VAR": "value" } } } } }}Extra hook directories:
{ "hooks": { "internal": { "load": { "extraDirs": ["/path/to/more/hooks"] } } }}CLI reference
Section titled “CLI reference”# List all hooks (add --eligible, --verbose, or --json)openclaw hooks list
# Show detailed info about a hookopenclaw hooks info <hook-name>
# Show eligibility summaryopenclaw hooks check
# Enable/disableopenclaw hooks enable <hook-name>openclaw hooks disable <hook-name>Best practices
Section titled “Best practices”- Keep handlers fast. Hooks run during command processing. Fire-and-forget heavy work with
void processInBackground(event). - Handle errors gracefully. Wrap risky operations in try/catch; do not throw so other handlers can run.
- Filter events early. Return immediately if the event type/action is not relevant.
- Use specific event keys. Prefer
"events": ["command:new"]over"events": ["command"]to reduce overhead.
Troubleshooting
Section titled “Troubleshooting”Hook not discovered
Section titled “Hook not discovered”# Verify directory structurels -la ~/.openclaw/hooks/my-hook/# Should show: HOOK.md, handler.ts
# List all discovered hooksopenclaw hooks listHook not eligible
Section titled “Hook not eligible”openclaw hooks info my-hookCheck for missing binaries (PATH), environment variables, config values, or OS compatibility.
Hook not executing
Section titled “Hook not executing”- Verify the hook is enabled:
openclaw hooks list - Restart your gateway process so hooks reload.
- Check gateway logs:
./scripts/clawlog.sh | grep hook
Related
Section titled “Related”- CLI Reference: hooks
- Webhooks
- Plugin hooks — in-process plugin lifecycle hooks
- Configuration