openclaw - ✅(Solved) Fix [Bug] Heartbeat model override ignored in v2026.3.28 - falls back to main session model [3 pull requests, 4 comments, 5 participants]

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…
GitHub stats
openclaw/openclaw#56788Fetched 2026-04-08 01:47:45
View on GitHub
Comments
4
Participants
5
Timeline
11
Reactions
1
Timeline (top)
cross-referenced ×5commented ×4referenced ×1subscribed ×1

PR fix notes

PR #57076: fix(heartbeat): respect heartbeat.model override — three-location fix

Description (problem / solution / changelog)

Summary

Problem: When agents.defaults.heartbeat.model is configured, the heartbeat ignores it and falls back to the main session default model. This is a recurring regression (previously reported in #9556, #13009, #14279, #21144, now #56788).

Why it matters: Users who want a cheaper or more capable model for autonomous heartbeat tasks cannot override the heartbeat model. The heartbeat always runs with the primary conversation model.

What changed: Three fixes across three files, each addresses a different point in the model resolution chain where the heartbeat model override is lost:

  1. src/plugins/runtime/runtime-system.tscreateRuntimeSystem was stripping heartbeat.model when forwarding to runHeartbeatOnceInternal. Added model to the passthrough.

  2. src/agents/live-model-switch.tsresolveLiveSessionModelSelection ignored caller-provided defaultProvider/defaultModel when agentId was present. Changed to prefer caller-provided defaults when both are present.

  3. src/auto-reply/reply/get-reply.ts — After resolveReplyDirectives, provider/model unconditionally overwrote the heartbeat model. Added guard: if (!hasResolvedHeartbeatModelOverride).

What did NOT change: Non-heartbeat conversation flows, cron job model resolution, subagent spawning, /model command.

Coordination with #57094

This PR overlaps with #57094 on live-model-switch.ts. @openperf independently found and fixed the same root cause with broader coverage (model-fallback.ts, agent-runner-execution.ts, retry cap).

Suggested merge order: #57094 first, then this PR.

Once #57094 merges, we'll rebase and drop the live-model-switch.ts change here. The remaining two fixes (runtime-system.ts/types-core.ts and get-reply.ts) are not covered in #57094 and are needed to close the full upstream call chain — without them, the model override is silently dropped before the live-switch logic is ever reached.

Change Type

  • Bug fix

Scope

  • Gateway
  • Agents

Linked Issue

Closes #56788

Related: #9556, #13009, #14279, #21144

Root Cause / Regression History

This bug has been reported and "fixed" multiple times (#9556, #13009, #14279, #21144). Previous fix attempts (#13016, #9429, #14956) only addressed one of three loss points and were closed without merging.

The heartbeat model is resolved correctly in getReplyFromConfig, but lost at three downstream points:

  1. Plugin runtime API strips the model field
  2. Live session model selection ignores caller defaults
  3. Directive resolution overwrites the heartbeat model

Regression Test Plan

  1. Set agents.defaults.heartbeat.model to a different model than default
  2. Trigger heartbeat via openclaw system event --text "test" --mode now
  3. Verify no live session model switch detected error in logs
  4. Verify heartbeat uses configured model
  5. Verify normal conversations still use default model

Security Impact

  • Authentication/authorization/secrets: No
  • Network/sandbox/filesystem: No
  • Exec/eval/code-gen: No
  • User input processing: No
  • Rate limiting/audit: No

Human Verification

Tested on two OpenClaw v2026.3.28 Docker instances. Both showed zero model switch errors after patching, normal conversation routing unaffected.

Risks and Mitigations

RiskMitigation
Patch 1 changes defaultModelRef priority for all callersOnly one caller exists, always provides both defaults with already-resolved values
Patch 3 skips directive model override during heartbeatHeartbeat messages are system-generated, never contain inline directives

Changed files

  • src/agents/live-model-switch.test.ts (modified, +51/-4)
  • src/agents/live-model-switch.ts (modified, +6/-6)
  • src/auto-reply/reply/get-reply.heartbeat-guard.test.ts (added, +140/-0)
  • src/auto-reply/reply/get-reply.ts (modified, +9/-2)
  • src/plugins/runtime/runtime-system.ts (modified, +1/-1)
  • src/plugins/runtime/types-core.ts (modified, +1/-1)

PR #57094: fix(agents): fix inverted model fallback order and spurious live session switches

Description (problem / solution / changelog)

Summary

  • Problem: In v2026.3.28, model fallback logic appears inverted for child/thread/cron sessions. The system attempts the fallback model first, then immediately triggers a "live session model switch" back to the primary model. This causes every request to be processed twice, leading to massive latency spikes (e.g., 393ms gateway latency, 59s nested lane queueing) and leaking session modelOverrides into child sessions. The issue is tracked in #57063 and #56788. Affected files: src/agents/live-model-switch.ts, src/agents/model-fallback.ts

  • Root Cause: The root cause lies in resolveLiveSessionModelSelection() (src/agents/live-model-switch.ts). When a child/heartbeat session has no explicit modelOverride in its session store, the function falls back to resolveDefaultModelForAgent() (which returns the config-level default model). It completely ignores the defaultProvider and defaultModel parameters passed by the caller (pi-embedded-runner/run.ts), which actually contain the correctly resolved model for the current run (including parent-session inherited overrides and heartbeat configs).

    Because the live selection incorrectly returns the config default instead of the inherited model, hasDifferentLiveSessionModelSelection returns true, throwing a LiveSessionModelSwitchError.

    Compounding this, in the agent-command fallback path (src/agents/model-fallback.ts), LiveSessionModelSwitchError is caught by runFallbackCandidate but not rethrown. It is treated as a candidate failure, causing the fallback loop to skip the correct inherited model and move to the next candidate (which happens to be the config default), effectively inverting the fallback order.

  • Fix:

    1. In live-model-switch.ts: Modified resolveLiveSessionModelSelection to honour the caller-supplied defaultProvider and defaultModel when the session entry lacks an explicit modelOverride. The config-level default is now only used if the caller defaults are missing. This ensures the live selection matches the runtime-resolved model, preventing spurious switch errors.
    2. In model-fallback.ts: Added an explicit check in runFallbackCandidate to immediately rethrow LiveSessionModelSwitchError. This error indicates a state change, not a model availability failure, and must never be swallowed by the fallback loop.
  • What changed:

    • src/agents/live-model-switch.ts: Updated fallback logic in resolveLiveSessionModelSelection to prioritize caller defaults over config defaults.
    • src/agents/model-fallback.ts: Added rethrow logic for LiveSessionModelSwitchError in runFallbackCandidate.
    • src/agents/live-model-switch.test.ts: Added 6 new test cases covering child sessions, heartbeat sessions, and explicit overrides.
    • src/agents/model-fallback.test.ts: Added 2 new test cases ensuring LiveSessionModelSwitchError is not swallowed.
  • What did NOT change (scope boundary):

    • The core auto-reply model resolution logic (resolveStoredModelOverride, resolveThreadParentSessionKey) remains untouched.
    • The session store data structure and merge logic are unchanged.
    • The general FailoverError coercion and retry backoff logic remain unchanged.

Reproduction

  1. Configure a primary model (e.g., anthropic/claude-opus-4-6) and a fallback model (e.g., google/gemini-3-flash-preview) in config.yaml.
  2. Start a session and explicitly set the model to the fallback via /model google/gemini-3-flash-preview.
  3. Trigger a nested subagent run or wait for a heartbeat/cron job in that session.
  4. Before fix: The logs will show live session model switch detected before attempt... google/gemini-3-flash-preview -> anthropic/claude-opus-4-6. The run will fail on the first attempt and retry, doubling the latency.
  5. After fix: The nested/cron session correctly inherits and uses google/gemini-3-flash-preview without triggering a live switch or fallback loop.

Risk / Mitigation

  • Risk: Changes to live model selection could potentially break explicit user /model commands if the override is ignored.
  • Mitigation: The fix explicitly checks Boolean(entry?.modelOverride?.trim()) and continues to honour explicit session store overrides. Comprehensive unit tests were added to verify that explicit overrides still take precedence over caller defaults, while inherited/heartbeat models correctly use the caller defaults.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway / orchestration
  • App: web-ui

Linked Issue/PR

Fixes #57063 Fixes #56788

Changed files

  • src/agents/live-model-switch.test.ts (modified, +225/-0)
  • src/agents/live-model-switch.ts (modified, +38/-8)
  • src/agents/model-fallback.test.ts (modified, +80/-0)
  • src/agents/model-fallback.ts (modified, +39/-0)
  • src/auto-reply/reply/agent-runner-execution.test.ts (modified, +53/-0)
  • src/auto-reply/reply/agent-runner-execution.ts (modified, +24/-3)

PR #1: fix: heartbeat model override ignored — 4 root causes (#56788)

Description (problem / solution / changelog)

Summary

Fixes agents.defaults.heartbeat.model config being ignored at runtime — heartbeat always falling back to the default model (e.g. Opus) regardless of what is configured.

Based on upstream openclaw/openclaw PRs #57094 and #57076, both currently open and unmerged.

Root causes (4 loss points in the model resolution chain)

  1. runtime-system.ts — heartbeat model field stripped when forwarding to runHeartbeatOnceInternal
  2. live-model-switch.tsresolveLiveSessionModelSelection() ignores caller-provided defaults, uses config default instead
  3. model-fallback.tsLiveSessionModelSwitchError swallowed as a candidate failure, inverts fallback order
  4. get-reply.ts — unconditionally overwrites heartbeat model after directive resolution

Changes

Applied via build-time patch script (patches/apply-heartbeat-model-fix.sh) in Dockerfile.base:

  • src/plugins/runtime/runtime-system.ts — pass model field through to runHeartbeatOnceInternal
  • src/plugins/runtime/types-core.ts — add model?: string to heartbeat type
  • src/agents/live-model-switch.ts — prefer caller-provided defaultProvider/defaultModel over config lookup
  • src/agents/model-fallback.ts — add isLiveSessionModelSwitchError() structural check + rethrowLiveSwitch param
  • src/auto-reply/reply/agent-runner-execution.ts — use structural check, pass rethrowLiveSwitch: true
  • src/auto-reply/reply/get-reply.ts — guard post-directive model overwrite with hasResolvedHeartbeatModelOverride

Testing

Patches verified against current openclaw/openclaw main (commit at clone time). Patch script exits non-zero if any substitution fails.

Upstream PRs pending — will remove patch and point directly to source once merged upstream.

References

  • Upstream issue: openclaw/openclaw#56788
  • Upstream PR (fallback): openclaw/openclaw#57094
  • Upstream PR (override): openclaw/openclaw#57076

Changed files

  • Dockerfile.base (modified, +4/-0)
  • patches/apply-heartbeat-model-fix.sh (added, +166/-0)

Code Example

{
  "agents": {
    "defaults": {
      "heartbeat": {
        "model": "openrouter/arcee-ai/trinity-large-preview:free"
      }
    }
  }
}

---

attempt:1  minimax/MiniMax-M2.5                           failed
attempt:2  openrouter/arcee-ai/trinity-large-preview:free  failed
attempt:3  openrouter/qwen/qwen3-coder:free               failed
attempt:4  openrouter/z-ai/glm-4.5-air:free             failed
attempt:5  openrouter/stepfun/step-3.5-flash:free        failed
attempt:6  ollama/kimi-k2.5:cloud                       failed
attempt:7  ollama/qwen3-coder-next:cloud               failed
attempt:8  ollama/glm-4.7:cloud                       failed
attempt:9  ollama/minimax-m2.7:cloud                   failed
attempt:10 minimax/MiniMax-M2.7                         succeeded
RAW_BUFFERClick to expand / collapse

Heartbeat model override is ignored at runtime. The configured heartbeat.model is not respected; heartbeat always falls back to the main session default model.

Environment

  • OpenClaw version: v2026.3.28 (commit f9b1079)
  • OS: WSL2, Node v22.22.1
  • Channel: WhatsApp
  • Primary model: minimax/MiniMax-M2.7

Configuration

{
  "agents": {
    "defaults": {
      "heartbeat": {
        "model": "openrouter/arcee-ai/trinity-large-preview:free"
      }
    }
  }
}

Expected Behavior

Heartbeat should use the configured model.

Actual Behavior

Heartbeat requests MiniMax-M2.5 (main session default), cycling through 10 candidates before landing on MiniMax-M2.7:

attempt:1  minimax/MiniMax-M2.5                           failed
attempt:2  openrouter/arcee-ai/trinity-large-preview:free  failed
attempt:3  openrouter/qwen/qwen3-coder:free               failed
attempt:4  openrouter/z-ai/glm-4.5-air:free             failed
attempt:5  openrouter/stepfun/step-3.5-flash:free        failed
attempt:6  ollama/kimi-k2.5:cloud                       failed
attempt:7  ollama/qwen3-coder-next:cloud               failed
attempt:8  ollama/glm-4.7:cloud                       failed
attempt:9  ollama/minimax-m2.7:cloud                   failed
attempt:10 minimax/MiniMax-M2.7                         succeeded

Additional Context

MiniMax deprecated M2, M2.1, M2.5, VL-01 in v2026.3.28, making M2.5 unreliable. This worsens the cascade.

Related

  • #21144 (closed/locked - same bug in v2026.2.19)
  • #14279, #13009, #9556 (previously fixed, regression)

extent analysis

Fix Plan

To resolve the issue of the heartbeat model override being ignored at runtime, we need to update the configuration to ensure that the specified model is used.

  • Update the heartbeat.model configuration to include the override flag:
{
  "agents": {
    "defaults": {
      "heartbeat": {
        "model": "openrouter/arcee-ai/trinity-large-preview:free",
        "override": true
      }
    }
  }
}
  • Verify that the openrouter/arcee-ai/trinity-large-preview:free model is available and compatible with the current OpenClaw version.
  • If the issue persists, try specifying the model version explicitly:
{
  "agents": {
    "defaults": {
      "heartbeat": {
        "model": "openrouter/arcee-ai/trinity-large-preview:[email protected]",
        "override": true
      }
    }
  }
}

Verification

To verify that the fix worked, check the heartbeat logs to ensure that the specified model is being used. The logs should show the openrouter/arcee-ai/trinity-large-preview:free model being used instead of the main session default model.

Extra Tips

  • Make sure to check the OpenClaw documentation for any updates or changes to the configuration options.
  • If the issue is still not resolved, try checking the model availability and compatibility with the OpenClaw version.
  • Consider updating the OpenClaw version to the latest available version to ensure that any known bugs or issues are resolved.

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…

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 - ✅(Solved) Fix [Bug] Heartbeat model override ignored in v2026.3.28 - falls back to main session model [3 pull requests, 4 comments, 5 participants]