fix(status): prefer active OAuth for runtime aliases

Prefer the active Claude CLI OAuth auth label when the configured Anthropic model resolves through an equivalent Claude CLI runtime alias, so `/status` no longer reports an unused env API-key label.

Also adds regression coverage for both text and message status renderers, plus the maintainer changelog entry.

Closes #80184.

Co-authored-by: brokemac79 <martin_cleary@yahoo.co.uk>
This commit is contained in:
brokemac79
2026-05-25 19:19:51 +01:00
committed by GitHub
parent 407cf8e328
commit 1e188bcda9
6 changed files with 137 additions and 8 deletions

View File

@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
- OpenShell/SSH: reject malformed generated exec commands before sandbox/session setup so unresolved workflow placeholders fail fast instead of reaching the remote shell. Fixes #72373. Thanks @brokemac79.
- Google: stop normalizing `gemini-3.1-flash-lite` to the retired preview endpoint and update Flash Lite alias guidance to the GA model id. Fixes #86151. (#86240) Thanks @SebTardif.
- Installer: make Alpine apk installs cover Git, verify the Node runtime floor, try `nodejs-current`, and report Alpine version guidance when repositories only provide older Node packages.
- Agents/status: prefer the active Claude CLI OAuth auth label over an unused Anthropic env API-key label for equivalent runtime aliases. Fixes #80184. (#86570) Thanks @brokemac79.
- Agents/media: send direct fallback for generated media still missing after an active requester wake fails. (#85489) Thanks @fuller-stack-dev.
- Agents: derive overflow compaction budgets from provider-reported and synthetic over-budget token counts so confirmed context overflows compact before retrying. (#70473) Thanks @fuller-stack-dev.
- Agents/Codex: recover Codex context-window prompt errors through overflow compaction and surface reset guidance when recovery is exhausted. (#85542) Thanks @fuller-stack-dev.

View File

@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { normalizeStaticProviderModelId } from "./model-ref-shared.js";
import { resolveModelRuntimePolicy } from "./model-runtime-policy.js";
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
@@ -182,6 +183,26 @@ export function areRuntimeModelRefsEquivalent(left: string, right: string): bool
);
}
export function shouldPreferActiveRuntimeAliasAuthLabel(params: {
runtimeAliasModelEquivalent: boolean;
selectedAuthLabel?: string;
activeAuthLabel?: string;
}): boolean {
if (!params.runtimeAliasModelEquivalent) {
return false;
}
const selectedAuth = normalizeOptionalLowercaseString(params.selectedAuthLabel);
const activeAuth = normalizeOptionalLowercaseString(params.activeAuthLabel);
if (!activeAuth || activeAuth === "unknown") {
return false;
}
return (
selectedAuth === "unknown" ||
(Boolean(selectedAuth?.startsWith("api-key")) &&
(activeAuth.startsWith("oauth") || activeAuth.startsWith("token")))
);
}
function resolveConfiguredRuntime(params: {
cfg?: OpenClawConfig;
provider: string;

View File

@@ -856,6 +856,52 @@ describe("buildStatusReply subagent summary", () => {
);
});
it("prefers active Claude CLI OAuth over selected env API-key labels for runtime aliases", async () => {
const text = await buildStatusText({
cfg: {
...baseCfg,
agents: {
defaults: {
agentRuntime: { id: "claude-cli" },
},
},
},
sessionEntry: {
sessionId: "sess-status-claude-cli-env-key-shadow",
updatedAt: 0,
providerOverride: "anthropic",
modelOverride: "claude-opus-4-7",
modelProvider: "claude-cli",
model: "claude-opus-4-7",
fallbackNoticeSelectedModel: "anthropic/claude-opus-4-7",
fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7",
fallbackNoticeReason: "selected model unavailable",
},
sessionKey: "agent:main:main",
parentSessionKey: "agent:main:main",
sessionScope: "per-sender",
statusChannel: "mobilechat",
provider: "anthropic",
model: "claude-opus-4-7",
contextTokens: 32_000,
resolvedHarness: "claude-cli",
resolvedFastMode: false,
resolvedVerboseLevel: "off",
resolvedReasoningLevel: "off",
resolveDefaultThinkingLevel: async () => undefined,
isGroup: false,
defaultGroupActivation: () => "mention",
modelAuthOverride: "api-key (env: ANTHROPIC_API_KEY)",
activeModelAuthOverride: "oauth (claude-cli)",
});
const normalized = normalizeTestText(text);
expect(normalized).toContain("Session selected: anthropic/claude-opus-4-7");
expect(normalized).toContain("oauth (claude-cli)");
expect(normalized).not.toContain("api-key (env: ANTHROPIC_API_KEY)");
expect(normalized).not.toContain("Usage:");
});
it("uses Codex OAuth context overrides for openai models running on the Codex harness", async () => {
registerStatusCodexHarness();

View File

@@ -995,6 +995,53 @@ describe("buildStatusMessage", () => {
expect(normalized).toContain("Context: 36k/1.0m (4%)");
});
it("prefers active CLI OAuth over selected env API-key labels for runtime aliases", () => {
const text = buildStatusMessage({
config: {
models: {
providers: {
anthropic: {
models: [
{
id: "claude-opus-4-7",
cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
},
],
},
},
},
} as unknown as OpenClawConfig,
agent: {
model: "anthropic/claude-opus-4-7",
},
sessionEntry: {
sessionId: "claude-cli-runtime-alias-env-key",
updatedAt: 0,
providerOverride: "anthropic",
modelOverride: "claude-opus-4-7",
modelProvider: "claude-cli",
model: "claude-opus-4-7",
fallbackNoticeSelectedModel: "anthropic/claude-opus-4-7",
fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7",
fallbackNoticeReason: "selected model unavailable",
inputTokens: 29,
outputTokens: 19_000,
},
sessionKey: "agent:main:main",
sessionScope: "per-sender",
queue: { mode: "collect", depth: 0 },
modelAuth: "api-key (env: ANTHROPIC_API_KEY)",
activeModelAuth: "oauth (anthropic:claude-cli)",
});
const normalized = normalizeTestText(text);
expect(normalized).toContain("Model: anthropic/claude-opus-4-7");
expect(normalized).toContain("oauth (anthropic:claude-cli)");
expect(normalized).not.toContain("api-key (env: ANTHROPIC_API_KEY)");
expect(normalized).not.toContain("Fallback: claude-cli/claude-opus-4-7");
expect(normalized).not.toContain("Cost:");
});
it("keeps an explicit runtime context limit when fallback status already computed one", () => {
const text = buildStatusMessage({
config: {

View File

@@ -2,7 +2,10 @@ import fs from "node:fs";
import { resolveContextTokensForModel } from "../agents/context.js";
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
import { resolveModelAuthMode } from "../agents/model-auth.js";
import { areRuntimeModelRefsEquivalent } from "../agents/model-runtime-aliases.js";
import {
areRuntimeModelRefsEquivalent,
shouldPreferActiveRuntimeAliasAuthLabel,
} from "../agents/model-runtime-aliases.js";
import {
buildModelAliasIndex,
resolveConfiguredModelRef,
@@ -925,8 +928,15 @@ export function buildStatusMessage(args: StatusArgs): string {
activeAuthMode && activeAuthMode !== "unknown"
? (args.activeModelAuth ?? activeAuthMode)
: undefined;
const selectedAuthLabelValue =
rawSelectedAuthLabelValue ?? (runtimeAliasModelEquivalent ? activeAuthLabelValue : undefined);
const preferActiveAuthLabel = shouldPreferActiveRuntimeAliasAuthLabel({
runtimeAliasModelEquivalent,
selectedAuthLabel: rawSelectedAuthLabelValue,
activeAuthLabel: activeAuthLabelValue,
});
const selectedAuthLabelValue = preferActiveAuthLabel
? activeAuthLabelValue
: (rawSelectedAuthLabelValue ??
(runtimeAliasModelEquivalent ? activeAuthLabelValue : undefined));
const fallbackState = resolveActiveFallbackState({
selectedModelRef: selectedModelLabel,
activeModelRef: activeModelLabel,

View File

@@ -10,7 +10,10 @@ import {
import { resolveContextTokensForModel } from "../agents/context.js";
import { resolveFastModeState } from "../agents/fast-mode.js";
import { resolveModelAuthLabel } from "../agents/model-auth-label.js";
import { areRuntimeModelRefsEquivalent } from "../agents/model-runtime-aliases.js";
import {
areRuntimeModelRefsEquivalent,
shouldPreferActiveRuntimeAliasAuthLabel,
} from "../agents/model-runtime-aliases.js";
import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../agents/openai-codex-routing.js";
import {
@@ -280,10 +283,11 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
modelRefs.active.label,
);
if (
runtimeAliasModelEquivalent &&
normalizeOptionalLowercaseString(selectedModelAuth) === "unknown" &&
activeModelAuth &&
normalizeOptionalLowercaseString(activeModelAuth) !== "unknown"
shouldPreferActiveRuntimeAliasAuthLabel({
runtimeAliasModelEquivalent,
selectedAuthLabel: selectedModelAuth,
activeAuthLabel: activeModelAuth,
})
) {
selectedModelAuth = activeModelAuth;
}