#Agent hook not working

1 messages · Page 1 of 1 (latest)

jolly crystal
#

I have external events that calls /hooks/wake to make my agent do certain things. Previously it works but recently I updated and the agent is not waking anymore. What can be a reason

raw talonBOT
#

First check: what does openclaw --version say?

Common causes after an update:

  • hooks.enabled is no longer effectively on, or hooks.token is missing. /hooks/wake only exists when hooks are enabled and authenticated.
  • Auth changed/was tightened: token must be in a header, not query params. Use Authorization: Bearer <token> or x-openclaw-token: <token>; /hooks/wake?token=... is rejected now.
  • Wrong method/body: it must be POST JSON with non-empty text, e.g. {"text":"New event","mode":"now"}. GET or blank text won’t wake.
  • You may be expecting an isolated agent run. /hooks/wake only enqueues a system event for the main session. If you want a standalone run, use /hooks/agent with message instead.
  • Heartbeat/session state: mode: "next-heartbeat" waits; mode: "now" should trigger immediately, but if the main lane is busy it may defer.

Quick debug:

openclaw status --all
openclaw gateway status
openclaw logs --follow

Then test locally from the gateway host:

jolly crystal
#

2026.05.07.

with logs follow and curl, curl return {"ok":true,"mode":"now"}, but log shows nothing. gateway just swallows the request?

raw talonBOT
#

{"ok":true,"mode":"now"} means the HTTP hook was accepted, but it does not guarantee the model turn actually ran.

For /hooks/wake, the code path is basically: enqueue a system event for the main session → request an immediate heartbeat. Successful wake requests are pretty quiet in logs, so “no log line” can be normal. The usual reason it looks swallowed is that the heartbeat runner immediately skips.

Things I’d check next:

openclaw system heartbeat last --json
openclaw status --deep
openclaw gateway status

And verify heartbeat config wasn’t disabled/changed:

openclaw config get agents.defaults.heartbeat.every
#
openclaw config get agents.defaults.heartbeat.target

agents.defaults.heartbeat.every: "0m" disables it. Also watch for active-hours / quiet-hours, main lane busy, or per-agent heartbeat config: if any agents.list[] entry has a heartbeat block, only those agents run heartbeats.

If you want a webhook to directly start work instead of relying on the main-session heartbeat path, try /hooks/agent:

curl -i -X POST http://127.0.0.1:18789/hooks/agent \
  -H 'Authorization: Bearer YOUR_HOOK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"message":"hook test","name":"Hook test","wakeMode":"now"}'

Docs: https://docs.openclaw.ai/automation/cron-jobs#webhooks and https://docs.openclaw.ai/gateway/heartbeat

jolly crystal
#

running heart beat last, I see it's been skipped

"ts": 1778599081609,
"status": "skipped",
"reason": "duplicate",
"preview": [redacted -- llm reasoning that it's duplicated],
"durationMs": 258,
"hasMedia": false,
"channel": "discord",
"accountId": "main"

how can that happen

raw talonBOT
#

That duplicate reason is heartbeat anti-nag dedupe, not HTTP hook dedupe.

In current code, after the heartbeat agent runs, OpenClaw compares the final outbound heartbeat text with the session’s stored lastHeartbeatText. If it’s identical, has no media, and the previous one was sent within 24h, it suppresses delivery and records:

status: "skipped", reason: "duplicate"

So the sequence can be:

  1. /hooks/wake accepts the request (ok: true)
  2. it queues the system event
  3. heartbeat runs the model
  4. model produces the same final visible payload as the previous heartbeat
  5. OpenClaw drops it as a duplicate

This can happen especially if your hook text is generic/repeated, or the heartbeat prompt makes the agent summarize it in the same words each time. The dedupe key is the agent’s final heartbeat message, not the raw hook request.

Workarounds:

#
  • Put unique detail in the hook text, e.g. event id/timestamp/object id, and ask it to include that if notifying.
  • Use /hooks/agent instead of /hooks/wake if each external event should directly run work. /hooks/wake is main-session heartbeat-driven.
  • If you’re intentionally repeating the same alert, vary the final notification text: “Event X fired at 15:22 UTC” instead of “Event fired”.

I’m a little worried this is too easy to mistake for “swallowed” because the request succeeds and only heartbeat last shows the skip. But with that duplicate reason, the gateway did wake; delivery got suppressed after the model returned the same heartbeat output.

jolly crystal
#

will agent see the "message" field in agent/wake calls?

raw talonBOT
#

For /hooks/agent: yes. The agent sees the message field as the isolated agent-turn prompt.

For /hooks/wake: no — /hooks/wake expects text, not message. That text is enqueued as an untrusted system event for the main session, then the heartbeat wakes. So use:

{"text":"New event abc123 happened","mode":"now"}

not:

{"message":"New event abc123 happened"}

If you want the payload to be the actual task prompt, use /hooks/agent:

#
{"message":"Process external event abc123 and report what changed","name":"External event","wakeMode":"now"}

Tiny distinction, but important: wake.text = event injected into main-session heartbeat; agent.message = direct prompt for an isolated run.

jolly crystal
#

so to make sure message goes different, I can have wake.text different?