openclaw - 💡(How to fix) Fix runHeartbeatOnce returns {status: "ran"} in 78ms on idle agent — phantom run, no model turn executed

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When calling runHeartbeatOnce on an idle agent (no active turn), it returns {status: "ran", durationMs: 78} but no model turn actually executes. The system event remains in the queue and is not delivered to the agent until the next scheduled heartbeat or external user message.

This is a different symptom than #84841 (which was about requestHeartbeat with intent: "immediate"). That issue described events sitting in queue without any heartbeat attempt. This issue describes heartbeats that appear to run successfully but execute no meaningful model turn.

Error Message

  1. The function returns {status: "ran", durationMs: 78} with no error
  • A silent error caught and returning an empty result that gets classified as "ok-empty" or "dismissed"

Root Cause

  • The same pattern affects the openclaw-interaction-bridge-v2 plugin which wakes an environmental agent on thermal presence events
  • Both plugins currently use enqueueSystemEvent + 300ms setTimeout + runHeartbeatOnce
  • The requestHeartbeatNow() API (which adds 250ms coalescence) would have the same root cause since it ultimately calls runHeartbeatOnce via the wake handler — the 300ms setTimeout in our plugin already provides more delay than requestHeartbeatNow's 250ms
  • hasHeartbeatWakeHandler() returns true — scheduled heartbeats work normally
  • This is a separate bug from #84841: that issue described no heartbeat attempt at all; this issue describes a phantom "ran" that executes nothing
  • Version: OpenClaw 2026.5.20 (e510042)

Code Example

api.runtime.system.enqueueSystemEvent("🎤 Voice input: test", { sessionKey: "agent:main:main" });
setTimeout(() => {
  api.runtime.system.runHeartbeatOnce({
    agentId: "main",
    sessionKey: "agent:main:main",
    reason: "hook",
    heartbeat: { target: "last" },
  }).then(result => console.log(result));
}, 300);

---

2026-05-24T00:20:08.019Z Enqueued via runtime API
2026-05-24T00:20:08.120Z runHeartbeatOnce result: {"status":"ran","durationMs":92}

2026-05-24T01:24:15.172Z Enqueued via runtime API
2026-05-24T01:24:15.385Z runHeartbeatOnce result: {"status":"ran","durationMs":104}

2026-05-24T13:59:19.348Z Enqueued via runtime API
2026-05-24T13:59:19.737Z runHeartbeatOnce result: {"status":"ran","durationMs":78}

2026-05-24T14:13:48.584Z Enqueued via runtime API
2026-05-24T14:13:48.998Z runHeartbeatOnce result: {"status":"ran","durationMs":106}

---

2026-05-23T22:52:14.778Z Enqueued via runtime API
2026-05-23T22:52:50.931Z runHeartbeatOnce result: {"status":"ran","durationMs":36144}

2026-05-23T23:05:05.984Z Enqueued via runtime API
2026-05-23T23:09:37.784Z runHeartbeatOnce result: {"status":"ran","durationMs":271791}

2026-05-24T14:20:46.778Z Enqueued via runtime API
2026-05-24T14:21:11.826Z runHeartbeatOnce result: {"status":"ran","durationMs":24739}
RAW_BUFFERClick to expand / collapse

Description

When calling runHeartbeatOnce on an idle agent (no active turn), it returns {status: "ran", durationMs: 78} but no model turn actually executes. The system event remains in the queue and is not delivered to the agent until the next scheduled heartbeat or external user message.

This is a different symptom than #84841 (which was about requestHeartbeat with intent: "immediate"). That issue described events sitting in queue without any heartbeat attempt. This issue describes heartbeats that appear to run successfully but execute no meaningful model turn.

Environment

  • OpenClaw 2026.5.20 (e510042) on Raspberry Pi (arm64)
  • Main agent: ollama/glm-5.1:cloud
  • Heartbeat config: agents.defaults.heartbeat.every: "30m", target: "last"
  • Plugin: openclaw-voice-bridge (calls runHeartbeatOnce after enqueueSystemEvent)

Reproduction

From a plugin:

api.runtime.system.enqueueSystemEvent("🎤 Voice input: test", { sessionKey: "agent:main:main" });
setTimeout(() => {
  api.runtime.system.runHeartbeatOnce({
    agentId: "main",
    sessionKey: "agent:main:main",
    reason: "hook",
    heartbeat: { target: "last" },
  }).then(result => console.log(result));
}, 300);

Evidence

Phantom runs (agent idle, no response produced):

2026-05-24T00:20:08.019Z Enqueued via runtime API
2026-05-24T00:20:08.120Z runHeartbeatOnce result: {"status":"ran","durationMs":92}

2026-05-24T01:24:15.172Z Enqueued via runtime API
2026-05-24T01:24:15.385Z runHeartbeatOnce result: {"status":"ran","durationMs":104}

2026-05-24T13:59:19.348Z Enqueued via runtime API
2026-05-24T13:59:19.737Z runHeartbeatOnce result: {"status":"ran","durationMs":78}

2026-05-24T14:13:48.584Z Enqueued via runtime API
2026-05-24T14:13:48.998Z runHeartbeatOnce result: {"status":"ran","durationMs":106}

Real runs (agent already active or wake succeeded):

2026-05-23T22:52:14.778Z Enqueued via runtime API
2026-05-23T22:52:50.931Z runHeartbeatOnce result: {"status":"ran","durationMs":36144}

2026-05-23T23:05:05.984Z Enqueued via runtime API
2026-05-23T23:09:37.784Z runHeartbeatOnce result: {"status":"ran","durationMs":271791}

2026-05-24T14:20:46.778Z Enqueued via runtime API
2026-05-24T14:21:11.826Z runHeartbeatOnce result: {"status":"ran","durationMs":24739}

The phantom runs complete in 78-106ms. Real model turns take 24-36+ seconds. The status: "ran" return value is misleading — no agent turn actually executed.

sessionKey and agentId used:

  • enqueueSystemEvent: sessionKey = "agent:main:main"
  • runHeartbeatOnce: agentId = "main", sessionKey = "agent:main:main", reason = "hook", heartbeat.target = "last"

Source Code Analysis

Traced through the heartbeat runner source (heartbeat-runner-s6x6k37Q.js in v2026.5.20):

  1. runHeartbeatOnce with reason: "hook" correctly sets isWakePayload: true, shouldBypassFileGates: true, shouldInspectPendingEvents: true
  2. The system event IS visible in the queue (queued via enqueueSystemEvent before runHeartbeatOnce)
  3. resolveHeartbeatRunPrompt generates a non-null prompt (wake payloads bypass file gates)
  4. getReplyFromConfig IS awaited — but completes in 78ms instead of 24+ seconds
  5. The function returns {status: "ran", durationMs: 78} with no error

The 78ms completion suggests getReplyFromConfig is returning immediately without actually calling the model. This could be:

  • A fast-path/short-circuit in the reply pipeline for idle sessions
  • The session being in a state where no model call is needed (e.g., empty context)
  • A silent error caught and returning an empty result that gets classified as "ok-empty" or "dismissed"

Expected behavior

runHeartbeatOnce should either:

  • Execute a real model turn (24-36s duration) and return the result, OR
  • Return {status: "skipped", reason: "..."} with a clear reason why no turn was executed

Additional context

  • The same pattern affects the openclaw-interaction-bridge-v2 plugin which wakes an environmental agent on thermal presence events
  • Both plugins currently use enqueueSystemEvent + 300ms setTimeout + runHeartbeatOnce
  • The requestHeartbeatNow() API (which adds 250ms coalescence) would have the same root cause since it ultimately calls runHeartbeatOnce via the wake handler — the 300ms setTimeout in our plugin already provides more delay than requestHeartbeatNow's 250ms
  • hasHeartbeatWakeHandler() returns true — scheduled heartbeats work normally
  • This is a separate bug from #84841: that issue described no heartbeat attempt at all; this issue describes a phantom "ran" that executes nothing
  • Version: OpenClaw 2026.5.20 (e510042)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

runHeartbeatOnce should either:

  • Execute a real model turn (24-36s duration) and return the result, OR
  • Return {status: "skipped", reason: "..."} with a clear reason why no turn was executed

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix runHeartbeatOnce returns {status: "ran"} in 78ms on idle agent — phantom run, no model turn executed