OpenClaw supports 16+ Hook types that can intercept and modify behavior at various stages.
HookTimingCan Block?before_model_resolveBefore model resolution❌before_prompt_buildBefore prompt building❌before_tool_callBefore tool call✅after_tool_callAfter tool call❌message_sendingBefore message sending✅ cancelmessage_sentAfter message sent❌subagent_spawningBefore subagent spawn❌subagent_endedAfter subagent ended❌
Record all tool calls to a file:
javascript
api.registerHook("before_tool_call", async ({ event, ctx }) => {
const log = {
time: new Date().toISOString(),
tool: event.tool.name,
params: event.tool.params,
session: ctx.sessionKey
};
// Write to log file
console.log("[TOOL_AUDIT]", JSON.stringify(log));
return {}; // Don't block, continue execution
});
Block dangerous tools in non-elevated sessions:
javascript
api.registerHook("before_tool_call", async ({ event, ctx }) => {
const dangerous = ["gateway", "cron", "nodes"];
if (dangerous.includes(event.tool.name) && !ctx.session.elevated) {
return {
block: true,
reason: `Tool '${event.tool.name}' requires elevated permissions`
};
}
return {};
});
Validate dangerous commands in exec tool:
javascript
api.registerHook("before_tool_call", async ({ event, ctx }) => {
if (event.tool.name === "exec") {
const cmd = event.tool.params.command || "";
const dangerous = ["rm -rf", "dd if=", "mkfs", ":(){:|:&}:"];
for (const d of dangerous) {
if (cmd.includes(d)) {
return {
block: true,
reason: `Dangerous command pattern detected: ${d}`
};
}
}
}
return {};
});
Switch models based on task type:
javascript
api.registerHook("before_model_resolve", async ({ event, ctx }) => {
const msg = event.messages?.[0]?.content || "";
if (msg.includes("write code") || msg.includes("debug")) {
return {
model: "ollama/deepseek-r1:70b",
provider: "ollama"
};
}
if (msg.includes("document") || msg.includes("summary")) {
return {
model: "ollama/qwen3:14b",
provider: "ollama"
};
}
return {};
});
Custom subagent result delivery:
javascript
api.registerHook("subagent_ended", async ({ event, ctx }) => {
// Do extra processing here
console.log("[SUBAGENT_ENDED]", event.result);
return {};
});
Register in the plugin's register(api):
javascript
export default definePluginEntry({
id: "my-hook-plugin",
name: "My Hook Plugin",
register(api) {
api.registerHook("before_tool_call", myHandler);
}
});
{ block: true } from a hook blocks the operationbefore_model_resolve can return { model, provider } to override