You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On openclaw 2026.5.19 (a185ca2, npm-installed), user-plugin hook handlers registered via api.registerHook(eventName, fn, meta) from plugins under ~/.openclaw/extensions/<id>/ show as ✓ ready in openclaw hooks list but the gateway never invokes them when the corresponding event fires. Reproduced with a minimal probe plugin (stderr-verify) registering gateway_start and before_agent_run; both register cleanly across multiple boot/reload cycles, neither executes during real channel turns. Observers registered via api.observe(...) from the same install path do fire normally (verified by message-audit's JSONL growing on the same turn).
Steps to reproduce
Install OpenClaw via npm install -g openclaw@latest (2026.5.19, a185ca2).
Create a minimal probe plugin (full source below) at ~/.openclaw/plugins/stderr-verify/ registering two hooks — gateway_start and before_agent_run — each appending a line to ~/.openclaw/stderr-verify-proof.txt from inside the handler body, plus emitting via api.logger.error/warn/info, console.error, and process.stderr.write. The plugin's register(api) callback writes a separate register() called line to the same file so registration can be distinguished from dispatch.
Restart: launchctl kickstart -k gui/501/ai.openclaw.gateway (macOS) or equivalent.
Confirm registration: openclaw hooks list shows stderr-verify-gateway-start and stderr-verify-before-agent-run as ✓ ready.
Send a real channel message (Telegram or WhatsApp DM) to the agent; the agent processes and replies (confirmed by message-audit JSONL appending dir:"in" + dir:"out" paired entries with success:true).
Inspect ~/.openclaw/stderr-verify-proof.txt and ~/Library/Logs/openclaw/gateway.err.log.
Per the SDK contract that register() calls api.registerHook(eventName, handler, meta) and that handlers run when the named event fires:
gateway_start handler should execute on every gateway boot / config-reload cycle. ~/.openclaw/stderr-verify-proof.txt should contain at least one gateway_start hook fired ts=... line per boot. ~/Library/Logs/openclaw/gateway.err.log should contain at least one STDERR_VERIFY_* marker per boot.
before_agent_run handler should execute on every agent turn (including channel-triggered turns). The proof file should grow by at least one before_agent_run hook fired line per processed inbound message.
This matches the documented hook lifecycle in the bundled plugin SDK types (PluginHookAgentContext is delivered to before_agent_run handlers; PluginHookMessageContext to message_sending handlers). It also matches how the bundled reply-only-hook plugin in the same install is documented to work (its gateway_start handler logs "reply-only-hook armed (dmScope=...)" — that string never appears in any log file on this install).
Actual behavior
Across multiple boot/reload cycles and at least two real Telegram inbound round-trips (both with paired success:true audit entries showing the agent replied), the probe handler body never executed.
Concretely, after a Telegram round-trip on 2026-05-22 UTC:
~/.openclaw/stderr-verify-proof.txt contained only register() called lines (3 of them, across boots and a config-reload — one of which landed between the two inbound messages, so at least the second turn definitively post-dated the most recent registration). No hook fired lines.
grep -c STDERR_VERIFY ~/Library/Logs/openclaw/gateway.err.log returned 0. None of the five emit paths (api.logger.error/warn/info, console.error, process.stderr.write) produced a marker. Since each path writes from inside the handler body, this independently confirms the handler did not run (had the body executed, at least one path would have left evidence; appendFileSync writes via OS fd independent of stderr routing, ruling out a stderr-sink explanation).
openclaw hooks list continued to show both handlers as ✓ ready throughout.
Same behavior observed when triggering via openclaw agent --agent main -m "..." CLI — handler body does not execute despite the agent producing a full reply.
Control evidence that the channel pipeline and observer surface are functioning normally:
message-audit plugin (also installed under ~/.openclaw/extensions/, uses api.observe('message:received', ...) / api.observe('message:sent', ...)) appends to its JSONL on every turn — confirmed for the same Telegram round-trips above.
Channel plugins ([telegram], [whatsapp]) emit [diag] lines to gateway.err.log normally.
Bundled hooks (source: openclaw-bundled, e.g. bootstrap-extra-files) produce visible side effects (workspace bootstrap file AGENTS.md is 16780 chars (limit 12000); truncating in injected context), suggesting they do dispatch — only source: plugin:* user-installed hooks are affected.
openclaw hooks list shows Hooks (N/N ready) with N including the probe hooks throughout, so the count itself is misleading as a liveness indicator — registered ≠ dispatched.
The bundled reply-only-hook plugin (cause-WhatsApp voice notes fail without explicit "audio/ogg; codecs=opus" mimetype #7 defense in this install's hardening track) uses the same api.registerHook surface; its gateway_start startup-invariant log line "reply-only-hook armed (dmScope=...)" is absent from every log file across the whole day, consistent with the same gap. The 26-case smoke test for that plugin passes because it calls handler functions directly with mocked api/ctx; it does not exercise gateway dispatch.
Suspected root cause space (not investigated upstream-side): the source: plugin:* registration path may not be wired into the same dispatcher as source: openclaw-bundled; or api.registerHook from a plugin module may register into a registry the dispatcher does not consult; or there is a config gate I have not found. Happy to add diagnostic output to the probe plugin if a maintainer wants to direct the next step.
Summary
On openclaw 2026.5.19 (a185ca2, npm-installed), user-plugin hook handlers registered via
api.registerHook(eventName, fn, meta)from plugins under~/.openclaw/extensions/<id>/show as✓ readyinopenclaw hooks listbut the gateway never invokes them when the corresponding event fires. Reproduced with a minimal probe plugin (stderr-verify) registeringgateway_startandbefore_agent_run; both register cleanly across multiple boot/reload cycles, neither executes during real channel turns. Observers registered viaapi.observe(...)from the same install path do fire normally (verified bymessage-audit's JSONL growing on the same turn).Steps to reproduce
npm install -g openclaw@latest(2026.5.19, a185ca2).~/.openclaw/plugins/stderr-verify/registering two hooks —gateway_startandbefore_agent_run— each appending a line to~/.openclaw/stderr-verify-proof.txtfrom inside the handler body, plus emitting viaapi.logger.error/warn/info,console.error, andprocess.stderr.write. The plugin'sregister(api)callback writes a separateregister() calledline to the same file so registration can be distinguished from dispatch.openclaw plugins install ~/.openclaw/plugins/stderr-verify.launchctl kickstart -k gui/501/ai.openclaw.gateway(macOS) or equivalent.openclaw hooks listshowsstderr-verify-gateway-startandstderr-verify-before-agent-runas✓ ready.message-auditJSONL appendingdir:"in"+dir:"out"paired entries withsuccess:true).~/.openclaw/stderr-verify-proof.txtand~/Library/Logs/openclaw/gateway.err.log.Probe plugin source (
index.mjs):Manifest (
openclaw.plugin.json):{ "id": "stderr-verify", "name": "Stderr Verify (PROBE)", "version": "0.0.1", "main": "./index.mjs" }package.json:{ "name": "stderr-verify", "version": "0.0.1", "type": "module", "openclaw": { "extensions": ["./index.mjs"] } }Expected behavior
Per the SDK contract that
register()callsapi.registerHook(eventName, handler, meta)and that handlers run when the named event fires:gateway_starthandler should execute on every gateway boot / config-reload cycle.~/.openclaw/stderr-verify-proof.txtshould contain at least onegateway_start hook fired ts=...line per boot.~/Library/Logs/openclaw/gateway.err.logshould contain at least oneSTDERR_VERIFY_*marker per boot.before_agent_runhandler should execute on every agent turn (including channel-triggered turns). The proof file should grow by at least onebefore_agent_run hook firedline per processed inbound message.This matches the documented hook lifecycle in the bundled plugin SDK types (
PluginHookAgentContextis delivered tobefore_agent_runhandlers;PluginHookMessageContexttomessage_sendinghandlers). It also matches how the bundledreply-only-hookplugin in the same install is documented to work (itsgateway_starthandler logs"reply-only-hook armed (dmScope=...)"— that string never appears in any log file on this install).Actual behavior
Across multiple boot/reload cycles and at least two real Telegram inbound round-trips (both with paired
success:trueaudit entries showing the agent replied), the probe handler body never executed.Concretely, after a Telegram round-trip on 2026-05-22 UTC:
~/.openclaw/audit/messages.2026-05-22.jsonlgained 4 lines (2 inbound, 2 outbound, alldir/channel/peer/sessionKeycorrect,success:true). Observers fire.~/.openclaw/stderr-verify-proof.txtcontained onlyregister() calledlines (3 of them, across boots and a config-reload — one of which landed between the two inbound messages, so at least the second turn definitively post-dated the most recent registration). Nohook firedlines.grep -c STDERR_VERIFY ~/Library/Logs/openclaw/gateway.err.logreturned0. None of the five emit paths (api.logger.error/warn/info,console.error,process.stderr.write) produced a marker. Since each path writes from inside the handler body, this independently confirms the handler did not run (had the body executed, at least one path would have left evidence;appendFileSyncwrites via OS fd independent of stderr routing, ruling out a stderr-sink explanation).openclaw hooks listcontinued to show both handlers as✓ readythroughout.Same behavior observed when triggering via
openclaw agent --agent main -m "..."CLI — handler body does not execute despite the agent producing a full reply.Control evidence that the channel pipeline and observer surface are functioning normally:
message-auditplugin (also installed under~/.openclaw/extensions/, usesapi.observe('message:received', ...)/api.observe('message:sent', ...)) appends to its JSONL on every turn — confirmed for the same Telegram round-trips above.[telegram],[whatsapp]) emit[diag]lines togateway.err.lognormally.source: openclaw-bundled, e.g.bootstrap-extra-files) produce visible side effects (workspace bootstrap file AGENTS.md is 16780 chars (limit 12000); truncating in injected context), suggesting they do dispatch — onlysource: plugin:*user-installed hooks are affected.OpenClaw version
2026.5.19 (a185ca2)
Operating system
macOS 15.6 (Darwin 24.6.0)
Install method
npm global —
npm install -g openclaw@latest./opt/homebrew/bin/openclawis a symlink to/opt/homebrew/lib/node_modules/openclaw/openclaw.mjs.Model
moonshotai/kimi-k2-instruct(primary),openrouter/anthropic/claude-sonnet-4.6(fallback).Provider / routing chain
openclaw → openrouter → moonshotai(primary); fallbackopenclaw → openrouter → anthropic.Additional context
openclaw hooks listshowsHooks (N/N ready)with N including the probe hooks throughout, so the count itself is misleading as a liveness indicator — registered ≠ dispatched.reply-only-hookplugin (cause-WhatsApp voice notes fail without explicit "audio/ogg; codecs=opus" mimetype #7 defense in this install's hardening track) uses the sameapi.registerHooksurface; itsgateway_startstartup-invariant log line"reply-only-hook armed (dmScope=...)"is absent from every log file across the whole day, consistent with the same gap. The 26-case smoke test for that plugin passes because it calls handler functions directly with mockedapi/ctx; it does not exercise gateway dispatch.source: plugin:*registration path may not be wired into the same dispatcher assource: openclaw-bundled; orapi.registerHookfrom a plugin module may register into a registry the dispatcher does not consult; or there is a config gate I have not found. Happy to add diagnostic output to the probe plugin if a maintainer wants to direct the next step.sendPolicyis intentionally prefix-based — this report is independent of that schema question; it is about hook dispatch, not policy semantics.