Automation
Writing hooks
Hook file layout, the handler contract, reply delivery, and the HOOK.md metadata fields. Part of the Hooks guide.
Writing hooks
This example replies to a reset command and writes a fixed log marker. It does not read message content, call a model, or contact an external service.
Hook structure
On the Gateway host, use a new managed hook directory. The following commands
assume the default state directory and that reset-greeting does not already
exist; choose another name rather than overwrite an existing hook.
mkdir -p ~/.openclaw/hooks/reset-greeting cat > ~/.openclaw/hooks/reset-greeting/HOOK.md <<'HOOK'---name: reset-greetingdescription: "Confirm that a reset hook ran"metadata: { "openclaw": { "events": ["command:new", "command:reset"] } }--- # Reset greeting Send a short confirmation after an authorized reset command.HOOK cat > ~/.openclaw/hooks/reset-greeting/handler.js <<'HANDLER'export default function handler(event) { if (event.type !== "command" || !["new", "reset"].includes(event.action)) { return; } console.log("[reset-greeting] reset hook ran"); event.messages.push("Reset hook ran.");}HANDLERA hook needs HOOK.md and a handler file. Discovery checks, in order,
handler.ts, handler.js, index.ts, then index.js, using the first file it
finds. The example uses JavaScript so no TypeScript types or SDK imports are
needed.
Enable and load it:
openclaw hooks info reset-greetingopenclaw hooks enable reset-greetingSend /new in a disposable conversation on a configured chat channel that can
route replies, such as a direct message to the bot. Expect Reset hook ran.
in that conversation and [reset-greeting] reset hook ran in Gateway logs.
/reset triggers the same example. Normal command authorization still applies.
Use an ordinary OpenClaw conversation, not an ACP-bound thread; bound sessions
delegate reset handling to their owning runtime. Do not use Control UI/webchat
or a sessions.reset RPC as the chat-reply check:
those paths do not deliver this hook's event.messages to the UI. The log marker
can still show that a reset event ran. See
Reply delivery for the exact boundary.
Disable the example when finished:
openclaw hooks disable reset-greetingDisabling leaves the files in place. To use a workspace directory instead, put
the two files in <workspace>/hooks/reset-greeting/, then explicitly enable the
hook. Workspace placement is not an agent sandbox or a guarantee that the
Gateway will load that workspace's hooks.
Handler implementation
A handler exports a function returning void or Promise<void>. The loader uses
the default export unless metadata.openclaw.export names another export.
Returned values do not block, cancel, or rewrite the operation.
Every event has these fields:
| Field | Meaning |
|---|---|
type |
Family: command, session, agent, gateway, or message |
action |
Action within the family, such as new or compact:before |
sessionKey |
Session correlation key; Gateway events use a Gateway key instead |
timestamp |
JavaScript Date when the event object was created |
context |
Event-specific data described under Event context highlights |
messages |
Initially empty string array; only certain producers consume it as replies |
Treat context as an observation, not a live state-editing API. Fields vary by
producer, and cfg is not present on every event. In particular, patch events
carry cloned snapshots. The explicit mutable exception is
agent:bootstrap's context.bootstrapFiles.
Reply delivery
Pushing to event.messages is not a general send-message API:
| Producer | What happens to event.messages |
|---|---|
Chat command handling for /new and /reset |
Awaits handlers, joins strings with blank lines, and attempts a reply to the originating channel/recipient, preserving account and thread context |
Gateway session reset/create RPCs that emit command:new or command:reset |
Handlers run, but messages are not routed as chat replies |
session:compact:before and session:compact:after |
Forwarded to the caller's compaction-notice callback when present; that callback owns delivery |
| All other core events | Ignored as replies, including /stop, automatic reset, message events, bootstrap, patch, and Gateway lifecycle events |
A missing recipient, unsupported route, send policy, or delivery failure can prevent a reply. Append messages before the handler's promise settles; detached work that pushes later can miss the producer's delivery step. To control normal agent replies or send cancellation, use the appropriate typed plugin hook.
HOOK.md format
HOOK.md uses YAML frontmatter followed by human-readable Markdown:
---name: my-hookdescription: "Short description of what this hook does"homepage: https://example.com/my-hookmetadata: { "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } }--- # My Hook Explain the side effects, configuration, and verification steps here.name defaults to the directory name; use a unique, stable name.
description is shown in reports. The following fields belong under
metadata.openclaw:
| Field | Contract |
|---|---|
events |
Event-key array. At least one is needed to register a handler. |
export |
Function export name; defaults to default. |
hookKey |
Config-entry key; defaults to the hook name. Discovery collisions still use the hook name. |
emoji |
Display emoji. |
homepage |
Documentation URL; overrides top-level homepage, website, or url. |
os |
Allowed Node platform names, for example darwin, linux, or win32. |
requires.bins |
Every named executable must be on PATH. |
requires.anyBins |
At least one named executable must be on PATH. |
requires.env |
Every named variable needs a nonblank process value or per-hook env value. |
requires.config |
Every dotted config path must be truthy. |
always |
Bypass binary, environment, and config requirements; does not bypass OS or enablement policy. |
install |
Informational install descriptors: kind is bundled, npm, or git; optional id, label, package, repository, and bins. This metadata does not install dependencies or make Git specs accepted by the CLI. |
Use hooks.internal.entries.<hookKey>.enabled to control activation, not a
top-level enabled flag in HOOK.md. For historical requirement metadata,
workspace.dir, browser.enabled, and browser.evaluateEnabled default to true
when absent. workspace.dir is not a new setting you need to add to your config.