Skip to content

Commit e55a808

Browse files
LZY3538claude
andcommitted
@
fix(status): surface auto-fallback model in status and session_status (#96126) When a session falls back to an alternate model via auto-fallback (modelOverrideSource: "auto"), both `openclaw status` and `session_status` silently showed the active fallback model without indicating it differs from the configured primary. The mismatch gate used hasUserPinnedModelSelection() which returns false for auto-fallback. - status.summary.ts: widen mismatch gate from hasUserPinnedModelSelection to entry?.modelOverride != null; emit distinct "fallback selected" reason alongside existing "session override" - status.command-sections.ts: add fallback-specific wording ("auto fallback" / "check provider availability") while keeping the modelSelectionReason filter intact (no false-positive null-reason rows) - status-message.ts: add sessionHasAutoFallback detection for the session_status RPC path; show "auto fallback" / "check provider" label instead of "pinned session" / "clear /model default" Co-Authored-By: Claude <[email protected]> @
1 parent bbd01c1 commit e55a808

6 files changed

Lines changed: 110 additions & 12 deletions

File tree

src/commands/status.command-sections.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,42 @@ describe("status.command-sections", () => {
172172
]);
173173
});
174174

175+
it("shows fallback-specific wording for auto-fallback model mismatches", () => {
176+
const lines = buildStatusModelSelectionLines({
177+
recent: [
178+
{
179+
key: "agent:main:telegram:chat-2",
180+
kind: "direct",
181+
updatedAt: 1,
182+
age: 5_000,
183+
model: "qwen3.6-blue",
184+
configuredModel: "minimax/MiniMax-M3",
185+
selectedModel: "ollama/qwen3.6-blue:35b-a3b",
186+
modelSelectionReason: "fallback selected",
187+
runtime: "OpenClaw Default",
188+
totalTokens: null,
189+
totalTokensFresh: false,
190+
remainingTokens: null,
191+
percentUsed: null,
192+
contextTokens: null,
193+
flags: [],
194+
},
195+
],
196+
shortenText: (value) => value,
197+
warn: (value) => `warn(${value})`,
198+
muted: (value) => `muted(${value})`,
199+
});
200+
201+
expect(lines).toEqual([
202+
"warn(Session agent:main:telegram:chat-2 is running ollama/qwen3.6-blue:35b-a3b (auto fallback); config primary is minimax/MiniMax-M3.)",
203+
" Configured default: minimax/MiniMax-M3",
204+
" Session selected: ollama/qwen3.6-blue:35b-a3b",
205+
" Reason: fallback selected",
206+
" Action: check provider availability or retry with /model",
207+
" Docs: https://docs.openclaw.ai/concepts/models#selection-source-and-fallback-behavior",
208+
]);
209+
});
210+
175211
it("maps health channel detail lines into status rows", () => {
176212
const rows = buildStatusHealthRows({
177213
health: { durationMs: 42 } as HealthSummary,

src/commands/status.command-sections.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -388,14 +388,20 @@ export function buildStatusModelSelectionLines(params: {
388388
const key = params.shortenText(sess.key, 48);
389389
const configured = sess.configuredModel ?? "unknown";
390390
const selected = sess.selectedModel ?? "unknown";
391+
const isFallback = sess.modelSelectionReason === "fallback selected";
392+
const intro = isFallback
393+
? `Session ${key} is running ${selected} (auto fallback); config primary is ${configured}.`
394+
: `Session ${key} is pinned to ${selected}; config primary ${configured} will apply to new/unpinned sessions.`;
395+
const reasonLine = ` Reason: ${sess.modelSelectionReason ?? "session override"}`;
396+
const clearLine = isFallback
397+
? " Action: check provider availability or retry with /model"
398+
: " Clear with: /model default";
391399
lines.push(
392-
params.warn(
393-
`Session ${key} is pinned to ${selected}; config primary ${configured} will apply to new/unpinned sessions.`,
394-
),
400+
params.warn(intro),
395401
` Configured default: ${configured}`,
396402
` Session selected: ${selected}`,
397-
` Reason: ${sess.modelSelectionReason ?? "session override"}`,
398-
" Clear with: /model default",
403+
reasonLine,
404+
clearLine,
399405
" Docs: https://docs.openclaw.ai/concepts/models#selection-source-and-fallback-behavior",
400406
);
401407
}

src/commands/status.summary.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ describe("getStatusSummary", () => {
677677
expect(summary.sessions.recent[0]?.modelSelectionReason).toBeNull();
678678
});
679679

680-
it("does not mark auto fallback model overrides as pinned session selections", async () => {
680+
it("marks auto fallback model overrides with a fallback reason label", async () => {
681681
vi.mocked(statusSummaryRuntime.resolveConfiguredStatusModelRef).mockReturnValue({
682682
provider: "zhipu",
683683
model: "glm-4.5-air",
@@ -704,7 +704,7 @@ describe("getStatusSummary", () => {
704704

705705
expect(summary.sessions.recent[0]?.configuredModel).toBe("zhipu/glm-4.5-air");
706706
expect(summary.sessions.recent[0]?.selectedModel).toBe("deepseek/deepseek-v4-flash");
707-
expect(summary.sessions.recent[0]?.modelSelectionReason).toBeNull();
707+
expect(summary.sessions.recent[0]?.modelSelectionReason).toBe("fallback selected");
708708
});
709709

710710
it("does not mark runtime-equivalent provider aliases as pinned mismatches", async () => {

src/commands/status.summary.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,9 @@ export async function getStatusSummary(
432432
selectedModelComparisonLabel,
433433
configuredSessionModelComparisonLabel,
434434
) &&
435-
hasUserPinnedModelSelection(entry);
436-
// Session rows show the live selected model but warn only for user-pinned differences.
435+
entry?.modelOverride != null;
436+
// Session rows show the live selected model and warn for user-pinned
437+
// differences as well as runtime fallback selections (#96126).
437438
const contextTokens =
438439
resolveContextTokensForModel({
439440
cfg,
@@ -493,7 +494,11 @@ export async function getStatusSummary(
493494
model,
494495
configuredModel: configuredSessionModelLabel,
495496
selectedModel: selectedModelLabel,
496-
modelSelectionReason: modelSelectionDiffers ? "session override" : null,
497+
modelSelectionReason: modelSelectionDiffers
498+
? hasUserPinnedModelSelection(entry)
499+
? "session override"
500+
: "fallback selected"
501+
: null,
497502
runtime,
498503
contextTokens,
499504
flags: buildFlags(entry),

src/status/status-message.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,51 @@ describe("buildStatusMessage context window", () => {
148148
expect(text).toContain("Context: 36k/1.0m");
149149
expect(text).not.toContain("Context: 36k/200k");
150150
});
151+
152+
it("shows auto-fallback override label when model differs from configured default", () => {
153+
const text = buildStatusMessage({
154+
config: {
155+
models: {
156+
providers: {
157+
"ollama-cloud": {
158+
baseUrl: "https://ollama.com",
159+
models: [
160+
statusTestModel("deepseek-v4-pro", "DeepSeek V4 Pro", 1_000_000),
161+
statusTestModel("qwen3.6-blue", "Qwen 3.6 Blue", 128_000),
162+
],
163+
},
164+
},
165+
},
166+
},
167+
agent: {
168+
model: "ollama-cloud/deepseek-v4-pro",
169+
contextTokens: 1_000_000,
170+
},
171+
configuredDefaultModelLabel: "ollama-cloud/deepseek-v4-pro",
172+
explicitConfiguredContextTokens: 1_000_000,
173+
runtimeContextTokens: 128_000,
174+
sessionEntry: {
175+
sessionId: "auto-fallback-qwen",
176+
updatedAt: 0,
177+
providerOverride: "ollama-cloud",
178+
modelOverride: "qwen3.6-blue",
179+
modelOverrideSource: "auto",
180+
modelOverrideFallbackOriginProvider: "ollama-cloud",
181+
modelOverrideFallbackOriginModel: "deepseek-v4-pro",
182+
modelProvider: "ollama-cloud",
183+
model: "deepseek-v4-pro",
184+
totalTokens: 50_000,
185+
totalTokensFresh: true,
186+
},
187+
sessionKey: "agent:main:telegram:direct:auto-fallback",
188+
sessionScope: "per-sender",
189+
queue: { mode: "steer", depth: 0 },
190+
modelAuth: "api-key",
191+
});
192+
193+
expect(text).toContain("Model: ollama-cloud/qwen3.6-blue");
194+
expect(text).toContain("auto fallback; config primary ollama-cloud/deepseek-v4-pro");
195+
expect(text).toContain("check provider");
196+
expect(text).not.toContain("pinned session");
197+
});
151198
});

src/status/status-message.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,15 +1074,19 @@ export function buildStatusMessage(args: StatusArgs): string {
10741074
const modelNote = channelModelNote ? ` · ${channelModelNote}` : "";
10751075
const configuredDefaultModelLabel = normalizeOptionalString(args.configuredDefaultModelLabel);
10761076
const sessionHasPersistedModelSelection = hasUserPinnedModelSelection(entry);
1077+
const sessionHasAutoFallback =
1078+
entry?.modelOverride != null && !hasUserPinnedModelSelection(entry);
10771079
const configDefaultDiffersFromSession =
1078-
sessionHasPersistedModelSelection &&
1080+
(sessionHasPersistedModelSelection || sessionHasAutoFallback) &&
10791081
configuredDefaultModelLabel &&
10801082
selectedModelLabel !== configuredDefaultModelLabel &&
10811083
!areRuntimeModelRefsEquivalent(selectedModelLabel, configuredDefaultModelLabel, {
10821084
config: args.config,
10831085
});
10841086
const overrideLabel = configDefaultDiffersFromSession
1085-
? ` · pinned session; config primary ${configuredDefaultModelLabel} · clear /model default`
1087+
? sessionHasPersistedModelSelection
1088+
? ` · pinned session; config primary ${configuredDefaultModelLabel} · clear /model default`
1089+
: ` · auto fallback; config primary ${configuredDefaultModelLabel} · check provider`
10861090
: "";
10871091
const modelLines = [
10881092
`🧠 Model: ${selectedModelLabel}${selectedAuthLabel}${modelNote}${overrideLabel}`,

0 commit comments

Comments
 (0)