OpenClaw hooks are the part of the system you reach for when an agent event should trigger a small, predictable side effect. A user starts a new session. A session gets compacted. A message goes out. The Gateway starts. A hook can notice that event and run a handler.
That sounds simple, and it should stay simple. The mistake is treating hooks as a general automation bucket. They are not cron jobs. They are not standing instructions. They are not a replacement for plugin policy. They are the reflex layer: short scripts that react when something already happened in the Gateway.
That distinction matters when you run AI agents on your own server. A self-hosted agent is useful because it can touch real systems: chats, files, schedules, local tools, APIs, and memory. But every side effect needs a home. Put it in the wrong place and you get duplicate reports, hidden policy rules, slow shutdowns, or handlers that never fire because the event name was one character off.
This guide explains what OpenClaw hooks are, what events they can see, how they differ from plugin hooks, and where they sit next to automations, heartbeat, and standing orders.
What OpenClaw hooks are
OpenClaw internal hooks are small scripts that run inside the Gateway when named events fire. The official hooks page lists examples such as `/new`, `/reset`, `/stop`, session compaction, Gateway lifecycle events, and message flow. You manage them with the `openclaw hooks` CLI.
A hook is installed as a directory with two parts:
my-hook/
├── HOOK.md
└── handler.ts
`HOOK.md` holds the hook metadata: name, description, events, requirements, config key, homepage, and related fields. The handler file can be `handler.ts`, `handler.js`, `index.ts`, or `index.js`.
The handler receives an event object. That event includes the type, action, session key, timestamp, a messages array, and event-specific context. A minimal handler checks the event it cares about, does its work, and returns quickly.
export default async function handler(event) {
if (event.type !== "command" || event.action !== "new") {
return;
}
console.log(`[audit] new session: ${event.sessionKey}`);
}
That shape is the point. Hooks are request handlers. The OpenClaw docs are direct about this: internal hook handlers should not own long-lived timers, watchers, sockets, or clients. If you need a long-running service, make it a plugin service or use typed Gateway lifecycle hooks.
The five event families
OpenClaw core emits a fixed set of internal hook events. A hook can subscribe to a specific key such as `command:new`, or to a bare family name such as `command` if it wants every action in that family.
Command events
Command hooks fire for slash-command lifecycle events:
- `command:new` when `/new` is issued.
- `command:reset` when `/reset` is issued.
- `command:stop` when `/stop` is issued.
- `command` for any command event.
These are good for audit logs, session snapshots, or operator notices. They are not a way to reinterpret every user message. They see command events, not the whole model turn.
Session events
Session hooks fire when OpenClaw resets or modifies session state. The docs list `session:auto-reset`, `session:compact:before`, `session:compact:after`, and `session:patch`.
This is where the built-in `session-memory` hook fits. On `/new`, `/reset`, daily reset, or idle expiry, it saves a slice of recent conversation to the workspace memory folder. That is a perfect hook job: something happened in the session lifecycle, and the side effect is bounded.
Agent and Gateway events
`agent:bootstrap` fires before workspace bootstrap files are injected. `gateway:startup`, `gateway:shutdown`, and `gateway:pre-restart` cover Gateway lifecycle. These events are useful when you want to prepare context, send a short restart notice, or run a startup routine.
Keep the handler tight. Gateway shutdown hooks are best-effort and bounded so the process can continue shutting down if a handler stalls.
Message events
Message events cover inbound and outbound flow:
- `message:received` for inbound channel messages.
- `message:transcribed` after audio transcription.
- `message:preprocessed` after media and link processing.
- `message:sent` after an outbound send attempt.
One detail is easy to miss: pushing strings into `event.messages` only produces visible chat replies for `command:new`, `command:reset`, and compaction status events. Other events ignore those pushed messages. If your `message:sent` hook pushes text and nobody sees it, the hook may be working exactly as documented.
Internal hooks vs plugin hooks
OpenClaw has two hook systems that solve different problems.
Internal hooks are file-based scripts described by `HOOK.md`. Use them for operator-managed side effects: save a snapshot on `/new`, log slash commands, send a restart notice, or annotate compaction.
Plugin hooks are typed extension points registered with `api.on(…)` inside an OpenClaw plugin. Use them when you need ordered middleware, policy, blocking, approval, or payload rewriting.
The docs give a clean split. If you want to save a snapshot after `/new`, internal hooks are fine. If you want to block a tool call, rewrite a prompt, cancel an outbound message, or require approval, use plugin hooks.
That is not a cosmetic API difference. Plugin hooks have contracts, priorities, matcher rules, timeouts, and decision results. For example, `before_tool_call` can rewrite tool parameters, block execution, or require approval. It also has a default 15-second per-handler timeout, and policy hook timeouts fail closed. That is exactly what you want for security-sensitive behavior.
Internal hooks are looser on purpose. They are better for the operator’s installed integrations than for runtime policy.
Hooks vs automations, heartbeat, and standing orders
OpenClaw has several automation surfaces. They overlap in plain English, but not in responsibility.
Use automations when time owns the trigger. Daily report at 9 AM, one-shot reminder in 20 minutes, weekly analysis, webhook-triggered job: those belong to OpenClaw Automations. Automations persist jobs in Gateway state and create background task records.
Use heartbeat when the agent needs periodic awareness with main-session context. Inbox checks, calendar awareness, and low-pressure monitoring fit heartbeat better than a pile of separate scheduled jobs.
Use standing orders when the agent needs durable authority and boundaries. A standing order says what the agent is allowed to do, when it should escalate, and what needs approval. It belongs in workspace instructions, usually `AGENTS.md`.
Use internal hooks when the Gateway event is already the trigger. A reset happened. A message was sent. Compaction started. The hook reacts.
Here is the practical rule: if you can phrase the trigger as “at this time”, use an automation. If you can phrase it as “whenever the agent is checking in”, use heartbeat. If you can phrase it as “the agent is always responsible for this program”, use a standing order. If you can phrase it as “when this Gateway event fires”, use a hook.
How to write and enable a hook
A custom internal hook starts with a directory. Put it under a managed hook directory such as `~/.openclaw/hooks/` if you want it shared across workspaces, or under `
The `HOOK.md` file declares the events:
---
name: command-audit
description: "Log OpenClaw slash commands"
metadata:
{ "openclaw": { "events": ["command"], "requires": { "bins": ["node"] } } }
---
Then the handler does the work:
export default async function handler(event) {
if (event.type !== "command") {
return;
}
console.log(JSON.stringify({
ts: event.timestamp,
action: event.action,
session: event.sessionKey
}));
}
Use the CLI to inspect and enable hooks:
openclaw hooks list
openclaw hooks info command-audit
openclaw hooks enable command-audit
openclaw hooks check
The docs note that internal hook discovery is skipped until hooks are configured. Enabling a hook, installing a hook pack, setting an extra hook directory, or setting the internal hooks flag opts the Gateway into discovery.
Built-in hooks worth knowing
OpenClaw ships several bundled hooks. Start with these before writing your own.
`session-memory` saves recent user and assistant messages to workspace memory on `/new`, `/reset`, daily reset, or idle expiry. If you want lightweight recall across sessions without indexing full transcripts, this is the obvious first hook.
`bootstrap-extra-files` injects additional recognized bootstrap files from configured paths. The docs call out an important boundary: only recognized bootstrap basenames are loaded, such as `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `BOOTSTRAP.md`, and `MEMORY.md`.
`command-logger` logs slash commands as JSON lines to the OpenClaw logs directory. It is simple, but useful when you need to audit operator behavior.
`compaction-notifier` sends visible chat notices when compaction starts and finishes. That sounds small until a long-running chat surface seems quiet. A visible compaction notice tells the user the agent is summarizing context and will continue.
`boot-md` runs `BOOT.md` at Gateway startup for each configured agent scope, if that file exists in the workspace.
Debugging hooks that do not fire
Most hook bugs are boring, which is good news. Check these before rewriting code.
First, verify the event name. OpenClaw core emits the event keys listed in the docs. A typo such as `command:nwe` leaves a hook dead unless some plugin emits that custom event. The loader warns for such names, and `openclaw hooks info
Second, verify discovery. Run `openclaw hooks list`. If your hook is not listed, check the directory shape. You need `HOOK.md` plus a handler file with a supported name.
Third, verify eligibility. A hook can declare required binaries, environment variables, config paths, or OS constraints. `openclaw hooks info
Fourth, restart the Gateway when needed. Hooks are loaded by the Gateway. A file sitting on disk is not the same thing as a loaded handler.
Finally, check whether the hook is trying to reply from an event that ignores `event.messages`. For most message and Gateway events, logging or an external side effect may happen, but pushed chat text will not show up.
Security and design rules
A hook runs inside the Gateway process, so treat it as trusted operator code. Keep the handler small, explicit, and easy to audit.
- Filter events early.
- Prefer specific event keys over broad family listeners.
- Wrap risky work in `try/catch` so one failure does not poison the event path.
- Do not keep long-lived sockets or watchers inside an internal hook.
- Use plugin hooks for blocking, approval, and rewriting.
- Use automations for schedules instead of homemade timers.
OpenClaw’s hook system is powerful because it is not magic. It gives the Gateway a predictable event surface, a filesystem-based installation model, and enough diagnostics to explain why a hook did or did not run.
FAQ about OpenClaw hooks
Are OpenClaw hooks the same as webhooks?
No. Internal hooks run inside the Gateway when OpenClaw events fire. Webhooks are external HTTP endpoints that let outside systems trigger work in OpenClaw.
Should I use internal hooks for tool-call approval?
No. Use typed plugin hooks such as `before_tool_call`. Internal hooks are for coarse command, session, Gateway, and message events. Tool-call policy needs the typed plugin hook system.
Can a workspace hook override a bundled hook?
No. The docs say workspace hooks can add new hook names, but they cannot override bundled, managed, or plugin-provided hooks with the same name.
Why does my hook run but not send a chat message?
`event.messages` is only delivered back to chat for `command:new`, `command:reset`, and compaction status events. Other event families ignore pushed messages.
What is a good first hook to enable?
For most self-hosted setups, start with `session-memory` or `command-logger`. They are low-risk, easy to inspect, and teach the hook model without adding policy complexity.
Final take
OpenClaw hooks are best when they stay close to the event that triggered them. Save memory when a session resets. Log commands when commands happen. Notify users when compaction starts. Send a restart notice before the Gateway restarts.
Do not make hooks carry the whole automation story. OpenClaw already has schedulers, heartbeat, standing orders, background tasks, Task Flow, and typed plugin hooks. Pick the surface that owns the trigger. Hooks own events.
Want more practical OpenClaw guides? Read the related wcblog.in drafts on cron vs heartbeat, context compaction, and agent memory.
