Skip to content

[Bug]: sessions.patch model:null leaves liveModelSwitchPending=true on disk; produces ghost model labels in /model after provider removal #83192

@tassiocamara

Description

@tassiocamara

Bug type

Behavior bug / partial state cleanup

Beta release blocker

No

Summary

sessions.patch with model: null returns success and clears modelOverride, providerOverride, and modelOverrideSource from the session entry, but leaves liveModelSwitchPending: true on disk. The orphan flag causes downstream resolvers (e.g. /model rendering) to reconstruct a ghost selection from the session's .jsonl trajectory, which produces nonsensical model labels in chat when the originally-pinned model/provider no longer exists in config.

This is distinct from #69951 (override silently ignored), #77322 (session.model cache survives /new), and #73090 (modelProvider metadata not normalized for CLI runtimes).

Environment

  • OpenClaw: 2026.5.12 (f066dd2)
  • Install: npm global (/usr/lib/node_modules/openclaw)
  • OS: Zorin OS 18.1 (Linux 6.17, x86_64)
  • Gateway: local, systemd --user
  • Channel where ghost label was observed: Telegram DM

Steps to reproduce

Pure gateway-call repro, no UI required.

SK='{"key":"agent:main:main"}'

# T0: confirm clean state
python3 -c "
import json
s = json.load(open('$HOME/.openclaw/agents/main/sessions/sessions.json'))['agent:main:main']
for k in ('modelOverride','providerOverride','modelOverrideSource','liveModelSwitchPending'):
    print(f'{k}: {s.get(k, \"<unset>\")}')
"

# T1: pin a model
openclaw gateway call sessions.patch --params \
  '{"key":"agent:main:main","model":"openai/gpt-5.5"}'

# T2: confirm all 4 fields are set
python3 -c "... (same as T0)"
# Output:
#   modelOverride: gpt-5.5
#   providerOverride: openai
#   modelOverrideSource: user
#   liveModelSwitchPending: True

# T3: clear pin
openclaw gateway call sessions.patch --params \
  '{"key":"agent:main:main","model":null}'

# T4: re-check fields
python3 -c "... (same as T0)"

Expected behavior

T4 should show all four fields as <unset>. sessions.patch model:null is the documented way to clear a pinned model and should leave the entry in the same shape as a fresh session.

Actual behavior

T4 output:

modelOverride: <unset>          ✓ cleared
providerOverride: <unset>       ✓ cleared
modelOverrideSource: <unset>    ✓ cleared
liveModelSwitchPending: True    ✗ ORPHANED

The orphan liveModelSwitchPending: true survives indefinitely until either (a) another sessions.patch with a concrete model triggers a successful live switch (which clears it via clearLiveModelSwitchPending), or (b) manual JSON edit.

Observed downstream impact

In my session this morning, the orphan flag manifested when sending /model in Telegram. The handler showed:

Current: openai/qwen-7b

qwen-7b was an alias I had defined hours earlier on a now-removed llm-vps provider. The provider entry and the alias had both been deleted from openclaw.json and the gateway restarted. The only remaining trace was the orphan liveModelSwitchPending: true on agent:main:main. The handler's resolveLiveSessionModelSelection path apparently mined the session's .jsonl trajectory for the most recent /new-style switch, found "qwen-7b" (now an unknown identifier), and fell back to the default provider (openai) to render the bogus openai/qwen-7b label.

Removing liveModelSwitchPending: true manually from the entry, then re-rendering /model, immediately produced the correct Current: openai/gpt-5.

Suggested fix direction

In the same code path that handles sessions.patch with model: null (clearing the pin), also call clearLiveModelSwitchPending for that session key. The function already exists at live-model-switch-CgybHLBX.js (bundled name) and is what model_spawn and live-switch retries use to consume the flag — it should be the canonical drain on explicit-clear as well.

A defensive measure in formatCurrentModelLine / resolveLiveSessionModelSelection (don't synthesize a selection from trajectory when the override fields are unset) would also prevent ghost labels even if the orphan flag persists for unrelated reasons.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.

    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