Skip to content

LiveSessionModelSwitchError loop in cron jobs: session entry field name mismatch between dispatch and live switch check #57191

@auto-wood

Description

@auto-wood

Bug Description

Cron jobs with sessionTarget: "isolated" and a payload.model override enter an infinite LiveSessionModelSwitchError loop when the agent's default model (in openclaw.json agents list) differs from the cron payload model.

Steps to Reproduce

  1. Set agent default model to zai/glm-5-turbo in openclaw.json:
"agents": { "list": [{ "id": "main", "model": { "primary": "zai/glm-5-turbo" } }] }
  1. Create a cron job with a different payload.model:
{
  "name": "test",
  "agentId": "main",
  "sessionTarget": "isolated",
  "payload": { "kind": "agentTurn", "message": "hello", "model": "minimax-portal/MiniMax-M2.7" }
}
  1. Run the cron job → LiveSessionModelSwitchError: Live session model switch requested: zai/glm-5-turbo

Root Cause

Field name mismatch between two code paths:

Cron dispatch (src/cron/isolated-agent/run-config.ts ~line 4861) writes:

cronSession.sessionEntry.modelProvider = provider;  // "minimax-portal"
cronSession.sessionEntry.model = model;              // "MiniMax-M2.7"

Embedded run (resolveLiveSessionModelSelection in auth-profiles.ts) reads:

const provider = entry?.providerOverride?.trim() || defaultModelRef.provider;
const model = entry?.modelOverride?.trim() || defaultModelRef.model;

Since providerOverride / modelOverride are never written by cron dispatch, the function always falls back to resolveDefaultModelForAgent() which returns the agent config model (glm-5-turbo). This doesn't match the cron payload model (M2.7), triggering LiveSessionModelSwitchError.

The outer catch handles the error by switching to glm-5-turbo, but then model fallback kicks in (M2.7 fails → glm-5 fails → kimi), and each fallback attempt re-triggers the live switch check, creating a loop until retry limit.

Environment

  • OpenClaw version: 2026.3.28 (f9b1079)
  • OS: macOS (arm64)
  • Node: v24.13.1

Suggested Fix

Either:

  1. In cron dispatch: Also write the override fields:
cronSession.sessionEntry.modelProvider = provider;
cronSession.sessionEntry.model = model;
cronSession.sessionEntry.providerOverride = provider;  // add
cronSession.sessionEntry.modelOverride = model;         // add
  1. In resolveLiveSessionModelSelection: Also check the non-override fields:
const provider = entry?.providerOverride?.trim() || entry?.modelProvider?.trim() || defaultModelRef.provider;
const model = entry?.modelOverride?.trim() || entry?.model?.trim() || defaultModelRef.model;

Option 2 is safer as it's backward compatible with any code path that writes either field name.

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