Skip to content

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

@suifatt7799-oss

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions