openclaw - 💡(How to fix) Fix [Bug]: heartbeat.model override not honored by follow-up runs within a heartbeat session (2026.5.18)

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…

agents.defaults.heartbeat.model is honored only for the initial heartbeat-triggered prompt. When the heartbeat agent's response triggers any sub-run within the same session (tool deny+retry, fallback walks, queued follow-ups), those sub-runs come back through the reply path as trigger="user" and re-resolve the model from agents.defaults.model.primary — leaking the bot's expensive primary model into what the operator intended to be a cheap heartbeat-only session.

This is adjacent to but distinct from the prior heartbeat-model bugs in this repo:

  • #75452 (heartbeat model persists into subsequent user turns — opposite direction)
  • #72940 (heartbeat ignored entirely from tick 1 — happens before any sub-run)
  • #58484 (heartbeat stuck on fallback after one failure — fallback resolver issue, not the trigger-relabel issue here)

This one only fires for follow-up runs inside an otherwise-honored heartbeat session.

Root Cause

agents.defaults.heartbeat.model is honored only for the initial heartbeat-triggered prompt. When the heartbeat agent's response triggers any sub-run within the same session (tool deny+retry, fallback walks, queued follow-ups), those sub-runs come back through the reply path as trigger="user" and re-resolve the model from agents.defaults.model.primary — leaking the bot's expensive primary model into what the operator intended to be a cheap heartbeat-only session.

This is adjacent to but distinct from the prior heartbeat-model bugs in this repo:

  • #75452 (heartbeat model persists into subsequent user turns — opposite direction)
  • #72940 (heartbeat ignored entirely from tick 1 — happens before any sub-run)
  • #58484 (heartbeat stuck on fallback after one failure — fallback resolver issue, not the trigger-relabel issue here)

This one only fires for follow-up runs inside an otherwise-honored heartbeat session.

Fix Action

Fix / Workaround

  1. dist/agent-runner.runtime-B9LwhObT.js:1308, 1399, 3511 — follow-up-run dispatch reads params.isHeartbeat to decide trigger: params.isHeartbeat ? "heartbeat" : "user". The trajectory shows trigger=user for sub-runs 2–64, so params.isHeartbeat is false at that point in the call chain. The flag from (2) is lost somewhere between the initial reply call and the agent's tool-retry-induced follow-up.

Workarounds

Code Example

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "anthropic/claude-sonnet-4-6",
        "fallbacks": ["openai/gpt-4o", "xai/grok-4", "google/gemini-2.5-pro",
                      "anthropic/claude-haiku-4-5", "openai/gpt-4o-mini",
                      "google/gemini-2.0-flash", "xai/grok-4-mini",
                      "anthropic/claude-opus-4-7"]
      },
      "heartbeat": {
        "every": "2h",
        "model": "anthropic/claude-haiku-4-5",
        "isolatedSession": true,
        "lightContext": true
      }
    }
  }
}

---

ts                 source     model              cost
18:32:36.470  heartbeat  claude-haiku-4-5   $0.05    ← override honored
19:05:41.566  user       claude-sonnet-4-6  $0.66    ← override violated
19:05:47.874  user       claude-sonnet-4-6  $0.17    ← override violated
... 56 more sonnet turns in same session, channel=heartbeat ...
RAW_BUFFERClick to expand / collapse

Bug type

Regression / latent bug

Beta release blocker

No

Summary

agents.defaults.heartbeat.model is honored only for the initial heartbeat-triggered prompt. When the heartbeat agent's response triggers any sub-run within the same session (tool deny+retry, fallback walks, queued follow-ups), those sub-runs come back through the reply path as trigger="user" and re-resolve the model from agents.defaults.model.primary — leaking the bot's expensive primary model into what the operator intended to be a cheap heartbeat-only session.

This is adjacent to but distinct from the prior heartbeat-model bugs in this repo:

  • #75452 (heartbeat model persists into subsequent user turns — opposite direction)
  • #72940 (heartbeat ignored entirely from tick 1 — happens before any sub-run)
  • #58484 (heartbeat stuck on fallback after one failure — fallback resolver issue, not the trigger-relabel issue here)

This one only fires for follow-up runs inside an otherwise-honored heartbeat session.

Environment

  • OpenClaw: 2026.5.18 (5884024)
  • OS: macOS 26.4.1 (arm64)
  • Node.js: v25.9.0
  • Runtime: gateway (dist/index.js gateway --port 18800)

Config (relevant fields)

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "anthropic/claude-sonnet-4-6",
        "fallbacks": ["openai/gpt-4o", "xai/grok-4", "google/gemini-2.5-pro",
                      "anthropic/claude-haiku-4-5", "openai/gpt-4o-mini",
                      "google/gemini-2.0-flash", "xai/grok-4-mini",
                      "anthropic/claude-opus-4-7"]
      },
      "heartbeat": {
        "every": "2h",
        "model": "anthropic/claude-haiku-4-5",
        "isolatedSession": true,
        "lightContext": true
      }
    }
  }
}

Note: sonnet is not in the fallback chain — confirming the leak is not a fallback walk to primary, it's a fresh resolver call returning agents.defaults.model.primary.

Steps to reproduce

  1. Configure as above (heartbeat.model = haiku, defaults.model.primary = sonnet).
  2. Put something in HEARTBEAT.md that makes the agent attempt a tool the heartbeat session doesn't approve — e.g., a shell command. (In my repro, the agent reads a checklist that asks it to run ps aux | grep ... and similar.)
  3. Wait for one heartbeat tick. The agent emits the tool call, hits "Exec approval is required, but Heartbeat does not support chat exec approvals", OC re-submits, and the retry storm begins.

Expected behavior

Every billed turn in a heartbeat session — initial prompt + any follow-ups — uses agents.defaults.heartbeat.model.

Actual behavior

The first turn in the session uses the haiku override correctly. Every subsequent turn in the same session uses agents.defaults.model.primary (sonnet). One real session on 2026-05-20:

ts                 source     model              cost
18:32:36.470  heartbeat  claude-haiku-4-5   $0.05    ← override honored
19:05:41.566  user       claude-sonnet-4-6  $0.66    ← override violated
19:05:47.874  user       claude-sonnet-4-6  $0.17    ← override violated
... 56 more sonnet turns in same session, channel=heartbeat ...

Trajectory cross-check for session 51bac282-…:

#session.startedprovidermodelIdtriggermessageProvider
118:32:19anthropicclaude-haiku-4-5heartbeatheartbeat
219:02:36anthropicclaude-sonnet-4-6userheartbeat
3–6219:05–19:34anthropicclaude-sonnet-4-6userheartbeat
63–6419:34openai / xaigpt-4o, grok-4userheartbeat

Every prompt.submitted body is literally [OpenClaw heartbeat poll]. These are heartbeat polls relabeled as trigger=user, not real user turns.

Across one 24-hour window: 116 billed turns, $15.87 leaked, 6 of 8 heartbeat sessions affected. The two clean sessions emitted only the initial haiku turn before the agent returned HEARTBEAT_OK — they never entered the retry path.

Where the bug lives (dist references for 2026.5.18)

  1. dist/get-reply-CXtCkTsR.js:4144–4170 — the agents.defaults.heartbeat.model override is read only inside else if (opts?.isHeartbeat) { … }. If opts.isHeartbeat is falsy, the override is silently skipped and resolution falls back to the defaults.

  2. dist/heartbeat-runner-Dd1Xn9LC.js:1240–1247 — the heartbeat scheduler builds replyOpts = { isHeartbeat: true, heartbeatModelOverride: heartbeat?.model, … } and calls getReplyFromConfig exactly once per tick. This is the only place in the dist bundle that sets isHeartbeat: true.

  3. dist/agent-runner.runtime-B9LwhObT.js:1308, 1399, 3511 — follow-up-run dispatch reads params.isHeartbeat to decide trigger: params.isHeartbeat ? "heartbeat" : "user". The trajectory shows trigger=user for sub-runs 2–64, so params.isHeartbeat is false at that point in the call chain. The flag from (2) is lost somewhere between the initial reply call and the agent's tool-retry-induced follow-up.

Possible fixes

Two clean options:

  • (a) Thread isHeartbeat through follow-up runs. Anywhere a follow-up run is constructed from inside a heartbeat-originated parent context, inherit isHeartbeat: true unconditionally.
  • (b) Anchor heartbeat.model at the session level. OC already has sessionKey: "agent:main:main:heartbeat" on the heartbeat session. Persisting the resolved heartbeat model on the SessionEntry would let createModelSelectionState find it without needing opts.isHeartbeat to be re-asserted on every sub-run.

(b) seems closer to existing intent — stored-model-override-KM_v-MUA.js:48 already checks params.isHeartbeat !== true || params.hasResolvedHeartbeatModelOverride === true and hasResolvedHeartbeatModelOverride is structured like it wants to be persisted but currently isn't.

Workarounds

  • Set agents.defaults.model.primary to a cheap model on heartbeat-only bots. The leak degrades to "cheap model in two places" rather than "cheap heartbeat + expensive everything-else".
  • Remove heartbeat.every to stop heartbeat firings entirely until fix lands.
  • Avoid HEARTBEAT.md prompts that ask the agent to call exec; the retry loop is the trigger for the leak (the bug exists regardless, but goes unobserved without follow-up runs).

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

Every billed turn in a heartbeat session — initial prompt + any follow-ups — uses agents.defaults.heartbeat.model.

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 [Bug]: heartbeat.model override not honored by follow-up runs within a heartbeat session (2026.5.18)