fix: normalize claude cli fallback config

This commit is contained in:
Peter Steinberger
2026-04-05 07:09:03 +01:00
parent f039bbf2aa
commit 37cc06f1fd
4 changed files with 49 additions and 12 deletions

View File

@@ -117,7 +117,7 @@ Docs: https://docs.openclaw.ai
- MS Teams: replace the deprecated Teams SDK HttpPlugin stub with `httpServerAdapter` so recurring gateway deprecation warnings stop firing and the Express 5 compatibility workaround stays on the supported SDK path. (#60939) Thanks @coolramukaka-sys.
- CLI/Commander: preserve Commander-computed exit codes for argument and help-error paths, and cover the user-argv parse mode in the regression tests so invalid CLI invocations no longer report success when exits are intercepted. (#60923) Thanks @Linux2010.
- Telegram/native command menu: trim long menu descriptions before dropping commands so sub-100 command sets can still fit Telegram's payload budget and keep more `/` entries visible. (#61129) Thanks @neeravmakwana.
- Agents/Claude CLI: keep non-interactive `--permission-mode bypassPermissions` when custom `cliBackends.claude-cli.args` override defaults, so cron and heartbeat Claude CLI runs do not regress to interactive approval mode. (#61114) Thanks @cathrynlavery and @thewilloftheshadow.
- Agents/Claude CLI: keep non-interactive `--permission-mode bypassPermissions` when custom `cliBackends.claude-cli.args` override defaults, including fallback resolution before the runtime plugin registry is active, so cron and heartbeat Claude CLI runs do not regress to interactive approval mode. (#61114) Thanks @cathrynlavery and @thewilloftheshadow.
- Agents/skills: skip `.git` and `node_modules` when mirroring skills into sandbox workspaces so read-only sandboxes do not copy repo history or dependency trees. (#61090) Thanks @joelnishanth.
- Android/Talk Mode: cancel in-flight `talk.speak` playback when speech is explicitly stopped, so stale replies stop starting after barge-in or manual stop. (#61164) Thanks @obviyus.
- Plugins/onboarding: write dotted plugin uiHint paths like Brave `webSearch.mode` as nested plugin config so `llm-context` setup stops failing validation. (#61159) Thanks @obviyus.

View File

@@ -1,4 +1,8 @@
export { CLAUDE_CLI_BACKEND_ID, isClaudeCliProvider } from "./cli-shared.js";
export {
CLAUDE_CLI_BACKEND_ID,
isClaudeCliProvider,
normalizeClaudeBackendConfig,
} from "./cli-shared.js";
export {
createAnthropicBetaHeadersWrapper,
createAnthropicFastModeWrapper,

View File

@@ -331,7 +331,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
expect(resolved?.config.resumeArgs).toContain("bypassPermissions");
});
it("keeps bundle MCP enabled for override-only claude-cli config when the plugin registry is absent", () => {
it("normalizes override-only claude-cli config when the plugin registry is absent", () => {
const registry = createEmptyPluginRegistry();
setActivePluginRegistry(registry);
@@ -342,6 +342,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
"claude-cli": {
command: "/usr/local/bin/claude",
args: ["-p", "--output-format", "json"],
resumeArgs: ["-p", "--output-format", "json", "--resume", "{sessionId}"],
},
},
},
@@ -352,6 +353,22 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
expect(resolved).not.toBeNull();
expect(resolved?.bundleMcp).toBe(true);
expect(resolved?.config.args).toEqual([
"-p",
"--output-format",
"json",
"--permission-mode",
"bypassPermissions",
]);
expect(resolved?.config.resumeArgs).toEqual([
"-p",
"--output-format",
"json",
"--resume",
"{sessionId}",
"--permission-mode",
"bypassPermissions",
]);
});
});

View File

@@ -1,3 +1,7 @@
import {
CLAUDE_CLI_BACKEND_ID,
normalizeClaudeBackendConfig,
} from "../../extensions/anthropic/api.js";
import type { OpenClawConfig } from "../config/config.js";
import type { CliBackendConfig } from "../config/types.js";
import { resolveRuntimeCliBackends } from "../plugins/cli-backends.runtime.js";
@@ -10,12 +14,20 @@ export type ResolvedCliBackend = {
pluginId?: string;
};
function resolveFallbackBundleMcpCapability(provider: string): boolean {
// Claude CLI consumes explicit MCP config overlays even when the runtime
// plugin registry is not initialized yet (for example direct runner tests or
// narrow non-gateway entrypoints).
return provider === "claude-cli";
}
type FallbackCliBackendPolicy = {
bundleMcp: boolean;
normalizeConfig?: (config: CliBackendConfig) => CliBackendConfig;
};
const FALLBACK_CLI_BACKEND_POLICIES: Record<string, FallbackCliBackendPolicy> = {
[CLAUDE_CLI_BACKEND_ID]: {
// Claude CLI consumes explicit MCP config overlays even when the runtime
// plugin registry is not initialized yet (for example direct runner tests
// or narrow non-gateway entrypoints).
bundleMcp: true,
normalizeConfig: normalizeClaudeBackendConfig,
},
};
function normalizeBackendKey(key: string): string {
return normalizeProviderId(key);
@@ -96,6 +108,7 @@ export function resolveCliBackendConfig(
cfg?: OpenClawConfig,
): ResolvedCliBackend | null {
const normalized = normalizeBackendKey(provider);
const fallbackPolicy = FALLBACK_CLI_BACKEND_POLICIES[normalized];
const configured = cfg?.agents?.defaults?.cliBackends ?? {};
const override = pickBackendConfig(configured, normalized);
const registered = resolveRegisteredBackend(normalized);
@@ -117,13 +130,16 @@ export function resolveCliBackendConfig(
if (!override) {
return null;
}
const command = override.command?.trim();
const config = fallbackPolicy?.normalizeConfig
? fallbackPolicy.normalizeConfig(override)
: override;
const command = config.command?.trim();
if (!command) {
return null;
}
return {
id: normalized,
config: { ...override, command },
bundleMcp: resolveFallbackBundleMcpCapability(normalized),
config: { ...config, command },
bundleMcp: fallbackPolicy?.bundleMcp,
};
}