What Are OpenClaw Hooks? (And Why They Change How You Work with AI Agents)
If you've ever watched an AI agent overwrite a file it shouldn't touch, or wished it would run your test suite automatically after every code change — OpenClaw hooks are the answer. They let you intercept, react to, and control agent behavior at precisely the moments that matter.
Hooks are small event-driven scripts that run inside the OpenClaw Gateway at specific points in an agent's lifecycle. Think of them as middleware for your AI agent — they sit between the agent and the tools it calls, giving you a programmable intercept layer.
There are two distinct types:
- Internal hooks — scripts that execute inside the Gateway process itself. They have direct access to session state, tool call metadata, and the agent's working context. Zero network overhead.
- Webhooks — HTTP callbacks that fire to an external endpoint when a lifecycle event occurs. The Gateway sends a POST request; your server handles the logic.
The practical difference: internal hooks are for fast, synchronous guardrails and local automation. Webhooks are for anything that needs to reach outside your machine — Slack notifications, CI systems, logging platforms.
Internal Hooks vs. Webhooks — Which One Do You Need?
| Factor | Internal Hook | Webhook |
|---|---|---|
| Execution location | Inside the Gateway process | External HTTP server |
| Latency | Near-zero (synchronous) | Network round-trip |
| Session state access | Direct | Serialized payload only |
| Best for | File guards, auto-formatting, local scripts | Slack alerts, audit logging, external APIs |
| Setup complexity | Low — just a directory + handler file | Medium — requires a running HTTP endpoint |
| Blocking agent execution | Yes (PreToolUse hooks can abort) | Typically async/non-blocking |
Decision rule: If your hook needs to prevent an action or read local session data, use an internal hook. If it needs to notify an external system and doesn't need to block the agent, use a webhook.
How OpenClaw Hook Discovery Works
The Gateway uses automatic directory scanning to discover hooks. On startup, it scans configured hook directories and loads any valid hook packages it finds.
Two important prerequisites before hooks activate:
- Hooks must be explicitly enabled — a hook directory alone is not enough
- At least one hook entry must be configured in your Gateway settings
This is a common confusion point. You can have a perfectly written hook sitting in the right directory, but if the Gateway hasn't been told to activate hooks, it silently ignores them.
Each hook package requires exactly two files:
HOOK.md— metadata file declaring the hook's name, version, description, lifecycle event subscriptions, and any required permissionshandler.ts(orhandler.js) — the implementation file containing the actual logic that runs when the event fires
The HOOK.md file is what the Gateway reads first during discovery. If it's malformed or missing required fields, the hook won't load — no error, just silence. This is the most common cause of "my hook isn't working" reports.
The Four Lifecycle Events Every Developer Should Know
| Event | When it fires | Common use |
|---|---|---|
| PreToolUse | Before the agent calls any tool | Block dangerous operations, validate inputs |
| PostToolUse | After a tool call completes | Run tests, format code, log results |
| Stop | When the agent session ends | Send notifications, flush logs, cleanup |
| SessionStart | When a new agent session begins | Load context, set guardrails, warm up state |
PreToolUse is the most powerful — it's the only event that can abort a tool call before it executes. If your hook returns a rejection signal during PreToolUse, the agent never calls the tool.
PostToolUse is the workhorse for automation. File written? Run your linter. Test modified? Execute the suite. Code committed? Trigger a build.
Step-by-Step: Writing Your First Custom Hook from Scratch
Most documentation shows you commands. This shows you the full path from nothing to a working hook.
Goal: Auto-run ESLint after every file write.
Step 1 — Create the hook directory
mkdir -p .openclaw/hooks/auto-lintStep 2 — Write the HOOK.md metadata
# auto-lint
**Version:** 1.0.0
**Event:** PostToolUse
**Description:** Runs ESLint on any file written by the agent
**Tools:** write_file, edit_file The Tools field scopes your hook to specific tool calls. Without it, the hook fires on every PostToolUse event — usually not what you want.
Step 3 — Implement handler.ts
import { PostToolUseEvent } from "@openclaw/sdk";
import { execSync } from "child_process";
export default function handler(event: PostToolUseEvent) {
const filePath = event.toolResult?.path;
if (!filePath) return;
try {
execSync(`npx eslint --fix "${filePath}"`, { stdio: "inherit" });
} catch (err) {
console.error(`[auto-lint] ESLint failed on ${filePath}`);
}
}Step 4 — Enable via CLI
openclaw hooks enable auto-lintStep 5 — Verify it loaded
openclaw hooks list You should see auto-lint with status enabled. Start a session, write a file, and watch the linter fire.
JavaScript vs. TypeScript for Hook Handlers — What to Choose in 2026
The SDK ships with full TypeScript typings, and as of the 2026 SDK releases, TypeScript is the recommended default for new hooks.
| Factor | TypeScript | JavaScript |
|---|---|---|
| Type safety | Full — event shapes are typed | None — runtime surprises |
| Compilation step | Required (tsc or esbuild) | None |
| SDK compatibility | First-class support | Supported but no autocomplete |
| Best for | Any hook that will be maintained or shared | Quick one-off scripts |
If you're writing a hook you'll commit to version control or share with a team, use TypeScript. For a throwaway local guardrail, plain JavaScript is fine — just name it handler.js and skip the compile step.
OpenClaw Hooks CLI Reference
| Command | Flags | What it does |
|---|---|---|
| openclaw hooks list | --json | Lists all discovered hooks and their status |
| openclaw hooks inspect <name> | — | Shows full metadata from HOOK.md + current config |
| openclaw hooks enable <name> | — | Activates a hook for the current project |
| openclaw hooks disable <name> | — | Deactivates without removing |
| openclaw hooks install <pack> | --yes, --dry-run | Installs a hook pack from registry |
| openclaw hooks update | --all, --dry-run | Updates installed hook packs |
Managing Hook Packs — Install, Update, and the --dry-run Workflow
Hook packs bundle multiple related hooks as a single installable unit. The install workflow verifies an integrity hash before writing anything to disk.
# Preview what would be installed without committing
openclaw hooks install productivity-pack --dry-run
# Install non-interactively (for CI environments)
openclaw hooks install productivity-pack --yes
# Update all installed packs
openclaw hooks update --allThe --dry-run flag is underused. Run it before any install or update to see exactly what files would change. In CI pipelines, pair --yes with --dry-run in a separate validation step before the real install.
Bundled Hooks Reference: What Ships with OpenClaw
| Hook | Default state | What it does | Best used when |
|---|---|---|---|
| session-memory | Enabled | Persists key context across sessions | Long-running projects with recurring tasks |
| additional bundled hooks vary by Gateway version | — | Run openclaw hooks list --builtin to see yours | — |
Run openclaw hooks inspect session-memory to see its full configuration options. Most bundled hooks ship with sensible defaults but expose config fields for customization.
Real-World Hook Use Cases (With Working Examples)
1. File Protection Guardrail (PreToolUse)
Prevent the agent from ever touching your .env file:
import { PreToolUseEvent } from "@openclaw/sdk";
export default function handler(event: PreToolUseEvent) {
const target = event.toolInput?.path ?? "";
if (target.includes(".env")) {
return { abort: true, reason: "Modification of .env files is blocked by policy." };
}
} Drop this in .openclaw/hooks/protect-env/ with the matching HOOK.md subscribing to PreToolUse scoped to write_file and edit_file. The agent receives the rejection reason in its context and will not retry.
2. Slack Notification on Session Stop
import { StopEvent } from "@openclaw/sdk";
export default async function handler(event: StopEvent) {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `OpenClaw session ended. Files modified: ${event.session.filesModified ?? 0}`
})
});
} Subscribe this hook to the Stop event. Every session end fires a Slack message with a summary. No external server needed — the Gateway process makes the outbound request directly.
3. Auto-Format + Test on PostToolUse
import { PostToolUseEvent } from "@openclaw/sdk";
import { execSync } from "child_process";
export default function handler(event: PostToolUseEvent) {
const file = event.toolResult?.path;
if (!file?.endsWith(".ts")) return;
execSync(`prettier --write "${file}"`);
execSync("npm test -- --passWithNoTests", { stdio: "inherit" });
} This fires after every TypeScript file write, formats it, then runs your test suite. Slow on large projects — scope it tightly using the Tools field in HOOK.md.
Hooks for Teams — Enforcing Guardrails Across a Shared Project
Commit your .openclaw/hooks/ directory to version control. Every developer who clones the repo automatically has the same hook configuration.
- Lock critical hooks to
enabledin project config — prevents teammates from accidentally disabling file guards - Use
HOOK.mddescriptions to document intent — treat them like code comments, teammates will read them - Scope hooks to tool-level granularity — broad hooks slow down all agent interactions, creating friction that causes teammates to disable them
- In monorepos, hooks in a parent directory apply to all nested projects unless overridden at the subdirectory level
CI/CD Integration — Running OpenClaw Hooks Non-Interactively
In GitHub Actions or any headless environment, the interactive confirmation prompt will hang your pipeline. Use --yes to skip it:
- name: Install hooks non-interactively
run: openclaw hooks install qa-pack --yes
- name: Run OpenClaw session
env:
OPENCLAW_HOOKS_ENABLED: "true"
SLACK_WEBHOOK_URL: secrets.SLACK_WEBHOOK_URL
run: openclaw run --task "audit dependencies" --yesSet OPENCLAW_HOOKS_ENABLED=true as an environment variable to activate hooks without interactive confirmation. This overrides the "at least one entry configured" requirement in CI mode.
Hook Security: What Runs, What It Can Access, and How to Stay Safe
This is the part most documentation skips entirely — and it's the most important section if you're installing community hook packs.
What hooks can access: Hook scripts inherit the full permissions of the Gateway process. If the Gateway runs as your user account, your hooks can read any file that account can read, make network requests, execute subprocesses, and access environment variables including secrets.
The risk of unreviewed hook packs: A malicious hook pack could exfiltrate your .env, your SSH keys, or your API tokens — all while appearing to do something benign like "format code."
Hook Pack Audit Checklist
Run through this before installing any third-party pack:
- ☐Read the full
HOOK.md— do the declared permissions match the stated purpose? - ☐Read every line of
handler.ts/js— look forfetch(),execSync,process.envaccess - ☐Check npm provenance if the pack is registry-distributed (
npm info <pack> --json | grep provenance) - ☐Verify the publisher's identity — is this a known maintainer or a fresh account?
- ☐Run
--dry-runfirst and review the file manifest - ☐Never install hook packs with
--yeswithout completing the above steps first
The openclaw hooks inspect command shows you the full source path of an installed hook — use it to re-review handler code after updates.
Troubleshooting OpenClaw Hooks — When They Don't Fire
Hook not discovered at all
- Verify the directory is inside a scanned hooks path:
openclaw hooks list --verbose - Confirm
HOOK.mdexists and is valid — missing required fields silently skip the hook - Check that hooks are enabled globally and at least one entry is configured
Hook discovered but not firing
- Run
openclaw hooks inspect <name>— verify theEventfield inHOOK.mdmatches the lifecycle event you expect - Check the
Toolsscope — if you scoped towrite_filebut the agent is callingcreate_file, the hook won't trigger - Confirm the hook status shows
enabled, notloaded(loaded means discovered but not active)
Hook fires but handler errors are silent
- Add explicit
try/catchblocks withconsole.errorlogging in your handler - Gateway logs are written to
~/.openclaw/logs/— check the most recent session log for[hook]prefixed lines - Use
openclaw hooks inspect <name> --logsto surface the last execution output
Hook slowing down every agent action
- Profile with
openclaw hooks list --timingto see per-hook execution time - Move synchronous
execSynccalls to async where the result doesn't need to block the agent - Scope hooks to specific tools rather than subscribing to all
PostToolUseevents
Take Your AI Agent Workflow Further with EasyClaw
OpenClaw hooks give you control at the agent level. EasyClaw gives you that control — plus a full desktop-native environment built for developers and content teams who need reliability, privacy, and speed without cloud dependency.
- ✓ Run hooks, agents, and automation entirely on your own machine — no data leaves your environment
- ✓ Native integration with your existing dev toolchain — linters, test runners, formatters, CI pipelines
- ✓ Visual hook management — enable, disable, and inspect hooks without memorizing CLI flags
- ✓ Team-ready: share hook configurations, lock guardrails, and audit session logs from one dashboard
Frequently Asked Questions
Q: Can a hook completely stop the agent from executing a tool call?
A: Yes — only PreToolUse hooks can abort a tool call. Return { abort: true, reason: "..." } from your handler and the Gateway prevents the tool from executing. The agent receives the reason string in its context. PostToolUse, Stop, and SessionStart hooks cannot abort actions retroactively.
Q: What happens if my hook handler throws an unhandled error?
A: By default, an unhandled error in a hook handler is logged to the Gateway session log but does not crash the agent session. The agent continues as if the hook didn't fire. This is by design — hooks should never block core agent functionality. Always wrap your handler logic in try/catch and handle errors explicitly so you have visibility into failures.
Q: Can I use async/await in hook handlers?
A: Yes, both PreToolUse and PostToolUse handlers support async functions. For PreToolUse, the Gateway awaits the handler before deciding whether to proceed — so async abort logic works correctly. Be mindful that long-running async operations in PreToolUse will delay every tool call, so keep them fast.
Q: Do hooks apply to all projects or just the one they're in?
A: Hooks placed in a project's .openclaw/hooks/ directory are project-scoped and only activate for sessions in that project. Global hooks can be placed in ~/.openclaw/hooks/ and apply across all projects. In monorepos, hooks in a parent directory apply to nested projects unless overridden at the subdirectory level.
Q: Is there a performance cost to running many hooks?
A: Every hook adds latency to the event it subscribes to. A well-scoped, fast hook (under 50ms) is imperceptible. Problems arise when hooks run heavy synchronous operations on every PostToolUse event without tool-level scoping. Use openclaw hooks list --timing to profile, scope hooks to specific tools in HOOK.md, and move non-blocking work to async where possible.
Q: Can hooks access secrets from environment variables securely?
A: Hooks inherit the full environment of the Gateway process, so process.env.MY_SECRET works inside any handler. For CI environments, inject secrets through your pipeline's secrets manager (e.g., GitHub Actions Secrets) rather than hardcoding them. Never commit secrets to HOOK.md or handler files — treat hook source files as code that will be reviewed and version-controlled.
Final Thoughts — Choosing the Right Hook Strategy for Your Workflow
| Scenario | Recommended approach |
|---|---|
| Solo dev — protect sensitive files | Internal hook, PreToolUse, scoped to write tools |
| Solo dev — auto-run tests | Internal hook, PostToolUse, scoped to test-adjacent file types |
| Team — enforce shared guardrails | Commit hooks to version control, lock critical hooks enabled in project config |
| Team — audit trail | Stop event hook posting to a shared logging endpoint |
| CI pipeline — automated sessions | --yes flag + OPENCLAW_HOOKS_ENABLED env var, --dry-run in validation step |
| External notifications | Webhook or Stop event hook with fetch() to Slack/PagerDuty |
Start with one hook that solves a real pain — a file guard or a post-write linter. Get it working end-to-end before layering more. The power of hooks compounds: a session with three well-scoped hooks running smoothly is significantly more reliable than one with ten poorly-scoped hooks you don't trust.
The highest-leverage first hook for most developers: a PreToolUse guard on your .env and secrets files. It takes ten minutes to write, zero maintenance to run, and eliminates an entire class of agent mistakes permanently.