openclaw - 💡(How to fix) Fix Cron isolated sessions: LiveSessionModelSwitchError when payload.model differs from agent default [2 comments, 3 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#57370Fetched 2026-04-08 01:50:33
View on GitHub
Comments
2
Participants
3
Timeline
4
Reactions
0
Author
Timeline (top)
commented ×2closed ×1locked ×1

Root Cause

The embedded agent runner's run loop calls resolvePersistedLiveSelection() on each iteration. This function calls resolveLiveSessionModelSelection(), which:

  1. Resolves defaultModelRef via resolveDefaultModelForAgent({cfg, agentId}) - returns the agent's primary model (Opus)
  2. Reads entry.providerOverride and entry.modelOverride from the session store
  3. Falls back to defaultModelRef when override fields are empty

The cron runner writes the payload model to entry.model / entry.modelProvider (runtime display fields), but never populates entry.providerOverride / entry.modelOverride. The live switch detection reads the override fields, finds them empty, resolves to the agent default (Opus), and sees a mismatch with the actual running model (Qwen). It throws LiveSessionModelSwitchError.

hasDifferentLiveSessionModelSelection() correctly returns false when the persisted selection is null (no session entry exists). But for cron sessions, entries DO exist (created by pre-run persistence) - they just have the model in the wrong fields.

Fix Action

Workaround

Patch the session store to copy model/modelProvider into providerOverride/modelOverride for all cron session entries:

import json
store = json.load(open('path/to/sessions.json'))
for key, entry in store.items():
    if 'cron' not in key:
        continue
    m = entry.get('model', '').strip()
    mp = entry.get('modelProvider', '').strip()
    if m and not (entry.get('modelOverride') or '').strip():
        entry['modelOverride'] = m
        if mp:
            entry['providerOverride'] = mp
with open('path/to/sessions.json', 'w') as f:
    json.dump(store, f, indent=2)

Must be re-run after creating new cron jobs with non-default models.

Code Example

import json
store = json.load(open('path/to/sessions.json'))
for key, entry in store.items():
    if 'cron' not in key:
        continue
    m = entry.get('model', '').strip()
    mp = entry.get('modelProvider', '').strip()
    if m and not (entry.get('modelOverride') or '').strip():
        entry['modelOverride'] = m
        if mp:
            entry['providerOverride'] = mp
with open('path/to/sessions.json', 'w') as f:
    json.dump(store, f, indent=2)
RAW_BUFFERClick to expand / collapse

Bug Description

Isolated cron jobs with payload.model set to a non-default model (e.g., bailian/qwen3.5-plus when agent default is anthropic/claude-opus-4-6) fail immediately with LiveSessionModelSwitchError on every run.

Version

OpenClaw 2026.3.28 (f9b1079)

Steps to Reproduce

  1. Configure an agent with primary model anthropic/claude-opus-4-6
  2. Create an isolated cron job with payload.model: "bailian/qwen3.5-plus"
  3. Wait for the job to fire (or trigger manually with cron run)
  4. Job fails with: LiveSessionModelSwitchError: Live session model switch requested: anthropic/claude-opus-4-6

Root Cause Analysis

The embedded agent runner's run loop calls resolvePersistedLiveSelection() on each iteration. This function calls resolveLiveSessionModelSelection(), which:

  1. Resolves defaultModelRef via resolveDefaultModelForAgent({cfg, agentId}) - returns the agent's primary model (Opus)
  2. Reads entry.providerOverride and entry.modelOverride from the session store
  3. Falls back to defaultModelRef when override fields are empty

The cron runner writes the payload model to entry.model / entry.modelProvider (runtime display fields), but never populates entry.providerOverride / entry.modelOverride. The live switch detection reads the override fields, finds them empty, resolves to the agent default (Opus), and sees a mismatch with the actual running model (Qwen). It throws LiveSessionModelSwitchError.

hasDifferentLiveSessionModelSelection() correctly returns false when the persisted selection is null (no session entry exists). But for cron sessions, entries DO exist (created by pre-run persistence) - they just have the model in the wrong fields.

Expected Behavior

payload.model on isolated cron jobs should work as documented. The cron runner should either:

  1. Write providerOverride/modelOverride (not just model/modelProvider) when persisting the pre-run session entry, OR
  2. The live switch detection should skip cron sessions, OR
  3. resolveLiveSessionModelSelection() should fall back to the caller's values instead of resolveDefaultModelForAgent() when the session has no override fields

Impact

Affects ALL isolated cron jobs where payload.model differs from the agent's primary model. In our deployment, 25 of 29 enabled cron jobs were broken after upgrading to 2026.3.28.

Workaround

Patch the session store to copy model/modelProvider into providerOverride/modelOverride for all cron session entries:

import json
store = json.load(open('path/to/sessions.json'))
for key, entry in store.items():
    if 'cron' not in key:
        continue
    m = entry.get('model', '').strip()
    mp = entry.get('modelProvider', '').strip()
    if m and not (entry.get('modelOverride') or '').strip():
        entry['modelOverride'] = m
        if mp:
            entry['providerOverride'] = mp
with open('path/to/sessions.json', 'w') as f:
    json.dump(store, f, indent=2)

Must be re-run after creating new cron jobs with non-default models.

extent analysis

Fix Plan

To fix the LiveSessionModelSwitchError issue in isolated cron jobs, we need to modify the cron runner to populate the entry.providerOverride and entry.modelOverride fields. Here are the steps:

  • Modify the cron runner to write providerOverride and modelOverride when persisting the pre-run session entry.
  • Update the resolveLiveSessionModelSelection() function to fall back to the caller's values instead of resolveDefaultModelForAgent() when the session has no override!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! fields.

Example code:

# In the cron!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! runner
def persist_pre_run_session(entry):
    # ...
    entry['modelOverride'] = payload.model
    entry['providerOverride'] = payload.modelProvider
    # ...

# In resolveLiveSessionModelSelection()
def resolveLiveSessionModelSelection(cfg, agentId, session_entry):
    # ...
    if not session_entry.get('modelOverride') and not session_entry.get('providerOverride'):
        # Fall back to the caller's values
        return cfg.model, cfg.modelProvider
    # ...

Verification

To verify the fix, create a new isolated cron job with a non-default model and check that it runs successfully without throwing a LiveSessionModelSwitchError.

Extra Tips

  • Make sure to update the cron runner to handle cases where the payload.model or payload.modelProvider is empty or null.
  • Consider adding logging or monitoring to detect and handle cases where the LiveSessionModelSwitchError still occurs.
  • Review the code changes to ensure they do not introduce any new issues or regressions.

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