Internal Hooks: Automate Commands and Lifecycle Events
Learn how internal hooks let you run compact JavaScript or TypeScript handlers inside the Gateway process for command and lifecycle automation. Covers choosing between internal hooks, plugin hooks, and bundled hooks.
Read this when
- You want event-driven automation for /new, /reset, /stop, or session and Gateway events
- You want to write, install, enable, or debug an internal hook
- You need to understand hook discovery, event data, or reply delivery
Hooks
Internal hooks are compact JavaScript or TypeScript handlers that execute inside the Gateway process whenever OpenClaw emits an event. Reach for them to persist session context, record reset commands, or carry out brief side effects tied to message and session lifecycle events. OpenClaw ships bundled hooks for common jobs, so no plugin has to be written to make use of them.
Choose the right surface
| You want to… | Use |
|---|---|
Save context on /new, log commands, or react to session and message events | Internal hooks (HOOK.md plus a handler), described here |
| Modify prompts, intercept tools, control replies, or use lifecycle contracts with priorities and return values | Plugin hooks through api.on(...) |
| Let another service start work through an HTTP request | Webhooks |
| Export telemetry rather than change behavior | Diagnostic events |
Each of these is a distinct system. Event handlers for this page are configured by hooks.internal, while HTTP ingress is configured by hooks.enabled. Names of internal events, for example message:received, differ from the typed plugin names, for example message_received.
Warning
Hook code is trusted, not confined to a sandbox. The Gateway process's filesystem, network, and environment access is available to it. Before enabling any hook, review its code, particularly when it comes from a workspace or a downloaded package.
Quick start
Begin with command-logger: no extra binaries and no model calls are needed, and it leaves you a concrete file to inspect. Run the commands below on the Gateway host, using the same profile and config that Gateway uses:
openclaw hooks list
openclaw hooks info command-logger
openclaw hooks enable command-logger
Hook config changes take effect without a restart under the default hybrid reload mode. If reload mode is off, either run openclaw gateway restart or restart a foreground Gateway yourself. When your configuration has several agents and no implicit owner, add --agent <id>.
In a conversation that is safe to reset, an authorized user can send /new or /reset. After that, inspect the log on the Gateway host:
tail -n 5 ~/.openclaw/logs/commands.log
A new JSON line carrying "action":"new" or "action":"reset" is what you are looking for, along with a recent timestamp and that conversation's sessionKey. If a custom state directory is in use, read <stateDir>/logs/commands.log instead. This is evidence that a handler ran; on its own, openclaw hooks check is not.
Session and sender identifiers end up in the log. If keeping those records is unwanted, turn the hook off once you have finished testing:
openclaw hooks disable command-logger
Eligible, enabled, and loaded
Treat these three checks as independent:
- Requirements satisfied: on the host performing the check, the hook's OS, binaries, environment, and config requirements all pass.
- Enabled by config: the per-hook/source policy permits it. Workspace hooks need explicit opt-in; when broad discovery is on, bundled and managed hooks do not need that per-hook flag.
- Loaded: the running Gateway picked the hook, imported its handler, and registered its events. The master switch and the configured name selection must also allow it.
The ready, eligible, and loadable fields reported by the CLI cover the first two checks plus a nonempty event list. What they do not prove is that the Gateway imported the handler, that the global selection contains it, or that its event has fired. Once changes are made, verify the real side effect or the hook-specific log.
On config reload, selected handlers are prepared first and then swapped in together. Should a selected handler fail to load, the previous handlers remain active. An event already in flight completes with its original handlers; events after that use the new selection. gateway:startup is not replayed by a reload.
Local, remote, and agent scope
The inventory of the selected Gateway is requested by hooks list, info, and check. When an implicit local Gateway is unavailable, or lacks the report method, it may fall back to local discovery. On failure, a configured remote Gateway or an explicit OPENCLAW_GATEWAY_URL does not fall back to the hooks on your laptop.
Local config is always what hooks enable and hooks disable inspect and modify. A remote Gateway is never updated by them over RPC. To change that host's hooks, run them on the Gateway host.
What --agent <id> selects is the workspace to inspect, not an isolated hook registry. The saved hooks.internal.entries.<hookKey> entry is global. Directory hooks from its selected workspace are loaded by the Gateway into a process-wide registry; merely inspecting some agent's hooks/ directory does not cause every such directory to be loaded. When a handler should act for one particular agent only, it must filter the event's agent or session. See Hook discovery.
Plugin hooks
Inside hooks list, plugin-managed internal hooks show up as plugin:<id>. They take part in this event system, yet the owning plugin is what you enable or disable, rather than toggling them via hooks enable or hooks disable. For typed api.on hooks, the directory loader's configured-name selection is neither a policy gate nor a replacement for activating the plugin.
Internal events are registered through the legacy api.registerHook API. Typed lifecycle names such as before_tool_call, message_received, or session_start are not invoked by it; registering any of those names emits a warning that points authors to api.on(...). Where a new integration needs typed lifecycle control, consult the Plugin hooks reference.
Best practices
For a single event, handlers execute in sequence: listeners registered for the family run first, then exact listeners, and within each group the order is the order of registration. The dispatcher waits for each handler, logs any error that is thrown and keeps going with the remaining handlers. Directory hooks offer no priority setting.
Different events are not serialized by this sequencing. Message notifications, patch notifications, and automatic reset work may run concurrently with other events and with agent processing. No general handler timeout, cancellation signal, durable event queue, automatic retry, or exactly-once guarantee exists. Work that is in flight can be lost if the process exits or restarts.
Keep side effects short and bounded. Await the work that belongs to the handler, set timeouts on network calls, limit data sizes, and design repeatable operations so retries do not duplicate their effects. Do not use void doHeavyWork(event) as a general solution: that work escapes the handler's wait/error boundary and can outlive its session or process. If work needs a durable job lifecycle, use an automation or service that owns it.
Filter unrelated events early and avoid logging message bodies, whole config objects, or credentials. Message and session data can be private. Keep only the minimum needed for the side effect, protect output files, and set retention. Long-lived timers, watchers, sockets, and clients belong to a plugin service with an explicit shutdown lifecycle, not a request/event handler.
CLI reference
See openclaw hooks for every public report and toggle option, JSON output fields, exit behavior, and install/update aliases.
Detailed topics
These pages hold the reference and how-to material that used to follow the quick start on this page.
| Page | Read it when |
|---|---|
| Writing hooks | You are writing a hook and need the file layout, handler contract, or HOOK.md fields. |
| Hook configuration and discovery | You are enabling hooks, narrowing the selection, or tracing discovery across sources. |
| Bundled hooks | You want a shipped hook and need its behavior, options, and checks. |
| Hook event types and context | You need an event key's trigger, wait behavior, or context fields. |
| Hook troubleshooting | A hook is not discovered, not eligible, or not executing. |
Where each section moved
Every section heading from the previous single-page version keeps its anchor here, so an existing link such as /automation/hooks#session-memory still resolves. Each entry points at the page that now holds the content.
- Writing hooks
- Hook structure
- Handler implementation
- Reply delivery
- HOOK.md format
- Configuration
- Hook discovery
- Hook packs
- Bundled hooks
- boot-md
- boot-md details
- bootstrap-extra-files
- bootstrap-extra-files config
- command-logger
- command-logger details
- compaction-notifier
- compaction-notifier details
- session-memory
- session-memory details
- Event types
- Event context highlights
- Message context
- Troubleshooting
- Hook not discovered
- Hook not eligible
- Hook not executing