Skip to content

Heartbeat per-turn model override persists after turn completes (2026.4.29) #75452

Description

@suifatt7799-oss

Summary

The per-turn agents.defaults.heartbeat.model override is applied as a session-level persistence rather than a scoped single-turn override. After the heartbeat turn completes, the override is not cleared, so subsequent user turns silently run on the heartbeat model instead of the configured primary.

Environment

  • OpenClaw: v2026.4.29 (a448042)
  • OS: macOS 26.4.1 (arm64)
  • Node.js: 25.8.2
  • Models: redacted (see Config below; happy to share privately if needed)

Note: this behavior may also exist in earlier versions — 2026.4.29 is just when I confirmed it. Maintainers may want to check git history beyond the 2026.4.29 baseline.

Config (redacted)

{
  "agents": {
    "defaults": {
      "heartbeat": {
        "every": "1h",
        "model": "<model-B>"
      }
    },
    "list": [{
      "id": "main",
      "model": {
        "primary": "<model-A>",
        "fallbacks": ["<model-B>", "<model-C>"]
      },
      "thinkingDefault": "max"
    }]
  }
}

Where:

  • <model-A> is a large-context reasoning model (~1M context window)
  • <model-B> is a smaller, faster model (~200k context window) — used both as the heartbeat model and the first fallback
  • <model-C> is a second fallback (~200k context window)

Expected Behavior

Heartbeat fires → uses <model-B> to process the heartbeat turn only → after the turn completes, the session's active model reverts to <model-A>.

Actual Behavior

Heartbeat fires → uses <model-B>the model override persists, the session stays on <model-B> indefinitely. Context window also drops from ~1M → ~200k. All subsequent user turns run on the wrong model.

Evidence

Before heartbeat (normal state):

🧠 Model: <model-A>
📚 Context: 80k/1.0m (8%)

After heartbeat (bug state — model stuck):

🧠 Model: <model-B>
📚 Context: 78k/200k (39%)

Note: Think: max from the main agent config is still in effect, but the model selection is wrong.

Workaround

session_status({ sessionKey: "current", model: "default" })

This resets the override and restores <model-A>. But it must be done manually after every heartbeat fire.

Impact

Every hourly heartbeat silently downgrades the user's main session from a 1M-context reasoning model to a 200k-context smaller model. The user may not notice until response quality degrades or context gets truncated unexpectedly.

Root Cause Hypothesis

The heartbeat per-turn model override (agents.defaults.heartbeat.model) is applied as a session-level persistence rather than a scoped single-turn override. After the heartbeat turn completes, the override is not cleared.

Steps to Reproduce

  1. Configure agents.defaults.heartbeat.model to a different model than agents.list[main].model.primary
  2. Wait for heartbeat to fire (or trigger via cron)
  3. Run session_status — model will show the heartbeat model, not the configured primary

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions