Skip to content

Commit db723d2

Browse files
committed
fix: fail closed for cli-backed btw fallback
1 parent 81c553e commit db723d2

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/agents/btw.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ vi.mock("./model-auth.js", () => ({
7777
}));
7878

7979
vi.mock("./model-runtime-aliases.js", () => ({
80+
isCliRuntimeAliasForProvider: ({ runtime, provider }: { runtime?: string; provider?: string }) =>
81+
runtime === "claude-cli" && provider === "anthropic",
8082
resolveCliRuntimeExecutionProvider: ({
8183
provider,
8284
cfg,
@@ -642,6 +644,29 @@ describe("runBtwSideQuestion", () => {
642644
expect(streamSimpleMock).toHaveBeenCalledTimes(1);
643645
});
644646

647+
it("fails closed instead of using direct provider auth for CLI-runtime alias models", async () => {
648+
await expect(
649+
runSideQuestion({
650+
cfg: {
651+
agents: {
652+
defaults: {
653+
models: {
654+
"anthropic/claude-opus-4-7": { agentRuntime: { id: "claude-cli" } },
655+
},
656+
},
657+
},
658+
} as never,
659+
model: "claude-opus-4-7",
660+
sessionKey: DEFAULT_SESSION_KEY,
661+
}),
662+
).rejects.toThrow(
663+
"/btw is not yet supported for claude-cli-backed models. The selected model is routed through claude-cli; OpenClaw will not fall back to direct anthropic auth because that would use a different backend than the active session.",
664+
);
665+
expect(getApiKeyForModelMock).not.toHaveBeenCalled();
666+
expect(streamSimpleMock).not.toHaveBeenCalled();
667+
expect(registerProviderStreamForModelMock).not.toHaveBeenCalled();
668+
});
669+
645670
it("does not let an auto-selected stale Anthropic profile suppress Claude CLI auth for BTW", async () => {
646671
const claudeAuthStore = {
647672
version: 1 as const,

src/agents/btw.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { EmbeddedBlockChunker, type BlockReplyChunking } from "./embedded-agent-
2727
import { resolveModelWithRegistry } from "./embedded-agent-runner/model.js";
2828
import { getActiveEmbeddedRunSnapshot } from "./embedded-agent-runner/runs.js";
2929
import { resolveEmbeddedAgentStreamFn } from "./embedded-agent-runner/stream-resolution.js";
30-
import { applyPreparedRuntimeAuthToModel } from "./provider-request-config.js";
3130
import { resolveAvailableAgentHarnessPolicy, selectAgentHarness } from "./harness/selection.js";
3231
import {
3332
resolveImageSanitizationLimits,
@@ -39,8 +38,10 @@ import {
3938
getApiKeyForModel,
4039
requireApiKey,
4140
} from "./model-auth.js";
41+
import { isCliRuntimeAliasForProvider } from "./model-runtime-aliases.js";
4242
import { ensureOpenClawModelsJson } from "./models-config.js";
4343
import { listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-routing.js";
44+
import { applyPreparedRuntimeAuthToModel } from "./provider-request-config.js";
4445
import { registerProviderStreamForModel } from "./provider-stream.js";
4546
import { stripToolResultDetails } from "./session-transcript-repair.js";
4647
import { sanitizeImageBlocks } from "./tool-images.js";
@@ -379,6 +380,26 @@ export async function runBtwSideQuestion(
379380
if (harness.id === "codex") {
380381
throw new Error(`Selected agent harness "${harness.id}" does not support /btw side questions.`);
381382
}
383+
const fallbackPolicy = resolveAvailableAgentHarnessPolicy({
384+
provider: params.provider,
385+
modelId: params.model,
386+
config: params.cfg,
387+
agentId: sessionAgentId,
388+
sessionKey: params.sessionKey,
389+
});
390+
const fallbackRuntime = fallbackPolicy.runtime.trim();
391+
if (
392+
isCliRuntimeAliasForProvider({
393+
runtime: fallbackRuntime,
394+
provider: params.provider,
395+
cfg: params.cfg,
396+
})
397+
) {
398+
throw new Error(
399+
`/btw is not yet supported for ${fallbackRuntime}-backed models. ` +
400+
`The selected model is routed through ${fallbackRuntime}; OpenClaw will not fall back to direct ${params.provider} auth because that would use a different backend than the active session.`,
401+
);
402+
}
382403

383404
const activeRunSnapshot = getActiveEmbeddedRunSnapshot(sessionId);
384405
const imageLimits = resolveImageSanitizationLimits(params.cfg);

0 commit comments

Comments
 (0)