2026-07-19

OpenClaw Hooks: Setup and Troubleshooting

Build OpenClaw hooks for command, session, message, and Gateway events with safe handlers, CLI verification, and practical debugging.

OpenClaw Hooks: Setup and Troubleshooting cover illustration

A hook can be installed, eligible, enabled, and still appear dead. OpenClaw checks discovery, requirements, event identity, and handler behavior at different points. Diagnose those contracts in that order. Do not begin by rewriting the handler or repeatedly restarting the Gateway; both moves erase useful distinctions between “not loaded,” “never matched,” and “ran but produced no visible effect.”

A read-only inspection of a running OpenClaw 2026.7.1-2 installation found six internal hooks. All six were eligible, loadable, and enabled by the effective configuration; five came from the bundled set and one from the managed directory. The configuration named three entries explicitly, while the internal-hook master switch allowed broader discovery. No hook declared an unknown event key. Those facts prove the loader has viable material. They do not prove that a particular event occurred or that a handler completed its side effect.

The absence of a hook line is weak evidence. A bounded sample of 800 structured Gateway events contained no hook-related record. With a healthy inventory and no controlled trigger in the sample window, that result is “not observed,” not “broken.” A useful hook test creates one harmless, identifiable event and checks the expected effect.

Choose the extension surface before writing code

OpenClaw uses the word hook for more than one integration surface. The surfaces are related, but they do not have interchangeable contracts. Picking the wrong one can produce perfectly valid code that never sees the event or cannot block the operation you care about.

NeedCorrect surfaceWhat it can do
React to /new, /reset, compaction, Gateway lifecycle, or message flowInternal hook with HOOK.mdRun operator-managed side effects inside the Gateway
Rewrite prompts, block tools, require approval, cancel delivery, or order middlewareTyped plugin hook registered with api.on(...)Return decisions under an explicit typed contract
Let an external system trigger work in OpenClawWebhookAccept an inbound HTTP request; it is not an internal event listener
Export telemetry without changing runtime policyDiagnostic eventsObserve a separate event bus

One boundary catches teams repeatedly: command:stop observes the user issuing /stop. It is not an agent-finalization policy gate. A plugin that must examine a natural final answer and request another pass belongs on the typed before_agent_finalize hook. Internal hooks are deliberately coarser.

Prerequisites: four contracts must hold

Discovery

The loader must find a valid hook directory and handler through a bundled, plugin, managed, extra-directory, or workspace source.

Eligibility

Required binaries, environment variables, config paths, and operating systems must all match the running Gateway.

Event identity

The declared event key must be real, specific enough, and emitted by the surface the handler expects.

Side effect

The handler must finish quickly, catch failures, and write or deliver something that the selected event is allowed to expose.

The contracts are sequential. A handler cannot compensate for a directory the loader never discovered. An eligible hook cannot react to command:nwe. A correctly matched message:sent hook can push a string into event.messages, yet the chat will ignore it because only a small set of replyable events deliver those strings.

Silence alone identifies none of the four.

Editorial illustration of a hook handler component beside a blueprint while a Gateway mechanism inspects its declared contract
A hook has two artifacts, but four runtime contracts. Inspect the loader and metadata before treating the handler as the only moving part.

Setup starts with the runtime inventory

Start with the runtime inventory rather than the filesystem you expect OpenClaw to scan. The list command resolves precedence, eligibility, enabled state, source, events, and unknown keys from the same installation that will load the handler.

openclaw hooks list --json
openclaw hooks check --json
openclaw hooks info session-memory --json

The inspected installation returned six hooks from two source classes. Five were bundled, one was managed, and every requirement check passed. The detailed bundled artifact subscribed to two command events, required one config path, had no missing requirement, and was loadable. That is stronger evidence than seeing a directory on disk.

Next, read the effective hook configuration. Do not assume every listed hook has an explicit entry. The master switch, hook entries, hook packs, legacy handlers, and extra directories can each opt the loader into discovery:

openclaw config get hooks.internal --json
openclaw config validate --json

In the observed configuration, hooks.internal.enabled was true and three entries were explicitly enabled. There were no extra directories and no legacy handler rows. The effective inventory still contained six enabled hooks. This is why a count of config entries is not the same thing as a count of runtime hooks.

Record the source in incident notes. A workspace hook cannot override a bundled, plugin, or managed hook with the same name. Managed hooks and configured extra directories share higher precedence. A same-name directory in the workspace may be ignored by design.

The smallest standalone hook has two files

A standalone hook directory contains metadata in HOOK.md and code in a supported handler file. For a managed hook, place the directory below ~/.openclaw/hooks/. Workspace hooks live below <workspace>/hooks/ and require explicit enablement.

my-hook/
├── HOOK.md
└── handler.ts

The front matter is the loader contract. Keep the event list narrow and declare requirements that would otherwise fail later inside the handler:

---
name: my-hook
description: "Record whether outbound delivery succeeded"
metadata:
  {
    "openclaw": {
      "events": ["message:sent"],
      "requires": { "bins": ["node"] }
    }
  }
---

# Delivery audit hook

Stores one content-free delivery result for operator review.

The handler should filter immediately and avoid copying message bodies, sender identifiers, or provider metadata unless the workflow truly requires them:

export default async function handler(event) {
  if (event.type !== "message" || event.action !== "sent") return;

  try {
    console.log(JSON.stringify({
      event: "message:sent",
      success: Boolean(event.context?.success),
      observedAt: new Date().toISOString(),
    }));
  } catch (error) {
    console.error("[my-hook] audit write failed", error);
  }
}

This example proves dispatch without retaining content. Replace the console record with a bounded local sink only after the event path works. If the eventual side effect calls an API or touches a database, give it a timeout and an idempotency key; hooks may run around retries, reconnects, and lifecycle transitions where duplicate work is more expensive than one extra log line.

Event names are an API, not descriptive prose

Core emits a fixed set of event keys. A bare family such as command receives every action in that family; a specific key such as command:new reduces overhead and makes the handler easier to reason about. A typo is not a new custom event. The loader warns about unknown names, and openclaw hooks info <name> exposes them.

FamilyRepresentative eventsOperational detail
Commandcommand:new, command:reset, command:stopNew and reset can deliver pushed reply messages; stop cannot
Sessionsession:compact:before, session:compact:after, session:patchCompaction events may publish status notices; patch context is cloned
Agentagent:bootstrapThe bootstrap-files array is mutable; keep additions inside the workspace contract
Gatewaygateway:startup, gateway:shutdown, gateway:pre-restartShutdown waits are best-effort and bounded
Messagemessage:received, message:transcribed, message:preprocessed, message:sentContexts can contain content and provider metadata; minimize retention

Only command:new, command:reset, and the two compaction events deliver strings pushed into event.messages. Other events ignore the array. If a message:sent hook “runs but says nothing,” inspect its real side effect before declaring dispatch broken.

Eligibility failures deserve their own diagnosis

Eligibility is static preflight. It answers whether the installed machine can load the hook, not whether the event has fired. A hook can require any combination of binaries, alternative binaries, environment values, config paths, or operating systems. Per-hook environment values may satisfy an env requirement, but they also become secrets available to handler code.

openclaw hooks check --json
openclaw hooks info my-hook --json

Read the missing object. Fix the exact category it names. Installing a binary cannot satisfy a missing config path; adding a config row cannot make a Darwin-only handler load on Linux. Avoid always: true as a blanket escape hatch. It bypasses eligibility checks and converts a clear preflight failure into a later runtime surprise.

Do not print the whole process environment while debugging. A hook that requires one token does not justify copying every Gateway secret into an incident log. Check the named requirement, record only present/absent, and redact values at the collection boundary.

Checklist: one event at a time

A useful test ladder adds one fact at a time. Keep the first pass read-only, then make one deliberate configuration change only if the inventory proves it is necessary.

  1. Inventory: run openclaw hooks list --json. Confirm source, enabled state, events, and unknown-event count.
  2. Eligibility: run openclaw hooks check --json and info. Resolve only the reported requirement.
  3. Configuration: validate config and confirm whether the master switch, an entry, a pack, or an extra directory enables discovery.
  4. Reload: after an actual enable, disable, install, or code change, restart the Gateway once so the loader sees the new snapshot.
  5. Controlled trigger: create the smallest harmless event that matches the exact key. Give the observation a timestamp and expected effect.
  6. Effect: inspect the bounded sink, not an unrelated chat reply. Confirm one result and check for duplication.

One trigger. One expected effect. One timestamp.

Editorial illustration of a signal passing through sequential discovery, eligibility, event, and handler verification gates
Advance one gate at a time. A green handler result does not retroactively prove the discovery path you intended, and a quiet log window does not disprove a healthy hook.

Enabling changes configuration:

openclaw hooks enable my-hook
openclaw config validate --json
# Restart the Gateway using your normal supervisor.
openclaw hooks info my-hook --json

The enable command turns on the named entry and the internal-hook master switch. It refuses missing, plugin-managed, or ineligible hooks. Plugin-managed internal hooks must be controlled through their owning plugin instead.

A green inventory can still lie

The Gateway was never reloaded

The CLI can update configuration while the running Gateway still holds the prior handler set. Restart once after a real hook change, then re-run info. Repeated restarts without a new observation only add noise.

The event cannot produce the expected reply

A handler may push a message on message:received and execute successfully, but OpenClaw ignores that pushed reply. Use an allowed event or write to an appropriate side-effect sink.

A family listener hid a spelling error elsewhere

A broad command listener may work while a second hook declares an invalid specific key. Inspect every hook’s unknownEvents field; one healthy hook does not validate another hook’s metadata.

The handler blocks command processing

Internal hooks run during Gateway event handling. Long network calls turn a convenient automation into user-visible latency. Queue heavy work in the background, cap its duration, and decide what happens when the queue is unavailable.

The side effect is not idempotent

A lifecycle event can coincide with retries or reconnects. “Send invoice,” “delete object,” and “create ticket” handlers need durable deduplication. The event timestamp alone is rarely a sufficient key.

Shutdown work exceeded its budget

Gateway shutdown and pre-restart hooks are bounded so a stalled handler cannot hold the process forever. Use those events for a short checkpoint or notice, not a database migration.

Operator handoff: the evidence worth keeping

A compact incident record makes the next review cheaper without copying conversations or credentials. Preserve the OpenClaw version, hook source class, declared event, eligibility result, config-enablement mechanism, controlled trigger time, observed side effect, and restart boundary. Hash custom hook artifacts when change control matters.

  • The selected surface matches the desired power: internal, typed plugin, webhook, or diagnostics.
  • The runtime inventory shows the expected source and no same-name precedence surprise.
  • Every missing requirement is resolved without recording secret values.
  • The declared event key exists in the current release.
  • The controlled trigger is harmless and has one expected observable result.
  • The handler catches failures, has a time budget, and avoids retaining message content by default.
  • External writes have an idempotency plan and a rollback owner.
  • The post-change Gateway reload is recorded exactly once.

The key operational judgment is simple: do not collapse four contracts into “the hook ran” or “the hook failed.” Discovery and eligibility are inventory facts. Event dispatch needs a controlled trigger. Handler success needs an observable, privacy-bounded effect. Keeping those claims separate turns hook debugging from guesswork into a short sequence of falsifiable checks.

Sources