From b3db1dba85fef5d4b7dee9b5ef43ba5eb190cd9c Mon Sep 17 00:00:00 2001 From: alkor2000 <131229172+alkor2000@users.noreply.github.com> Date: Fri, 29 May 2026 02:50:36 +0800 Subject: [PATCH] fix(anthropic): stop migrating current claude-haiku-4-5 to sonnet (#87719) Summary: - The branch preserves current Claude Haiku 4.5 refs in the Anthropic resolver and doctor migration, repoints the bare `haiku` family alias to `claude-haiku-4-5`, and updates regression tests. - PR surface: Source +5, Tests +21. Total +26 across 4 files. - Reproducibility: yes. Current main source maps the bare `haiku` alias and explicit Haiku 4.5 migration path ... de-sonnet-4-6`; the PR body also supplies before/after terminal proof for the resolver and migration tests. Automerge notes: - No ClawSweeper repair was needed after automerge opt-in. Validation: - ClawSweeper review passed for head 64429e23b3ccf20c2821b2151081b2e5e8d4a8bb. - Required merge gates passed before the squash merge. Prepared head SHA: 64429e23b3ccf20c2821b2151081b2e5e8d4a8bb Review: https://github.com/openclaw/openclaw/pull/87719#issuecomment-4566419633 Co-authored-by: alkor2000 <200923177@qq.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com> --- extensions/anthropic/claude-model-refs.ts | 9 ++++---- extensions/anthropic/cli-migration.test.ts | 22 +++++++++++++++++++ .../shared/legacy-config-migrate.test.ts | 7 +++--- ...legacy-config-migrations.runtime.models.ts | 10 ++++++--- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/extensions/anthropic/claude-model-refs.ts b/extensions/anthropic/claude-model-refs.ts index 9946867d53e..0b689954983 100644 --- a/extensions/anthropic/claude-model-refs.ts +++ b/extensions/anthropic/claude-model-refs.ts @@ -4,7 +4,7 @@ import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_MODEL_ALIASES } from "./cli-constants const DEFAULT_CLAUDE_MODEL_BY_FAMILY: Record = { opus: "claude-opus-4-7", sonnet: "claude-sonnet-4-6", - haiku: "claude-sonnet-4-6", + haiku: "claude-haiku-4-5", }; export type ClaudeCliAnthropicModelRefs = { @@ -117,6 +117,10 @@ function upgradeOldClaudeModelId(normalized: string): string | null { if (normalized.startsWith("claude-sonnet-4-6") || normalized.startsWith("claude-sonnet-4.6")) { return null; } + // claude-haiku-4-5 is a current production model and must not be migrated. + if (normalized.startsWith("claude-haiku-4-5") || normalized.startsWith("claude-haiku-4.5")) { + return null; + } if ( normalized === "claude-opus-4" || hasAnyRetiredVersionPrefix(normalized, [ @@ -140,8 +144,6 @@ function upgradeOldClaudeModelId(normalized: string): string | null { "claude-sonnet-4.1", "claude-sonnet-4-0", "claude-sonnet-4.0", - "claude-haiku-4-5", - "claude-haiku-4.5", ]) || /^claude-sonnet-4-20\d{6}/.test(normalized) ) { @@ -172,7 +174,6 @@ function upgradeOldClaudeModelId(normalized: string): string | null { normalized === "sonnet-3.7" || normalized === "sonnet-3.5" || normalized === "sonnet-3" || - normalized === "haiku-4.5" || normalized === "haiku-3.5" || normalized === "haiku-3" ) { diff --git a/extensions/anthropic/cli-migration.test.ts b/extensions/anthropic/cli-migration.test.ts index 857c4fcb734..dad8b369153 100644 --- a/extensions/anthropic/cli-migration.test.ts +++ b/extensions/anthropic/cli-migration.test.ts @@ -55,6 +55,28 @@ describe("anthropic Claude model refs", () => { expect(resolveKnownAnthropicModelRef("anthropic/claude-sonnet-4-7")).toBe( "anthropic/claude-sonnet-4-7", ); + expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4-5")).toBe( + "anthropic/claude-haiku-4-5", + ); + }); + + it("preserves the current claude-haiku-4-5 model and its bare alias", () => { + // claude-haiku-4-5 is a current production model (not retired), so neither + // its full ref, its dotted variant, nor the bare "haiku" family alias must + // be rewritten to sonnet. + expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4-5")).toBe( + "anthropic/claude-haiku-4-5", + ); + expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4.5")).toBe( + "anthropic/claude-haiku-4.5", + ); + expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4-5@anthropic:work")).toBe( + "anthropic/claude-haiku-4-5@anthropic:work", + ); + // Genuinely retired Claude 3 Haiku still upgrades to the current sonnet. + expect(resolveKnownAnthropicModelRef("anthropic/claude-3-5-haiku-20241022")).toBe( + "anthropic/claude-sonnet-4-6", + ); }); }); diff --git a/src/commands/doctor/shared/legacy-config-migrate.test.ts b/src/commands/doctor/shared/legacy-config-migrate.test.ts index 172ba70c6da..c58903fe91e 100644 --- a/src/commands/doctor/shared/legacy-config-migrate.test.ts +++ b/src/commands/doctor/shared/legacy-config-migrate.test.ts @@ -1707,7 +1707,7 @@ describe("legacy model compat migrate", () => { }, }); - expect(res.config?.agents?.defaults?.imageModel).toBe("anthropic/claude-sonnet-4-6"); + expect(res.config?.agents?.defaults?.imageModel).toBe("anthropic/claude-haiku-4-5"); expect(res.config?.agents?.defaults?.imageGenerationModel).toEqual({ primary: "github-copilot/claude-sonnet-4.6", fallbacks: ["github-copilot/gpt-5.4-mini"], @@ -1751,6 +1751,7 @@ describe("legacy model compat migrate", () => { }); expect(res.config?.agents?.defaults?.workspace).toBe("/tmp/claude-3-sonnet"); expect(res.config?.agents?.defaults?.models).toEqual({ + "anthropic/claude-haiku-4-5": { alias: "haiku" }, "anthropic/claude-sonnet-4-6": { alias: "current-sonnet" }, "github-copilot/claude-opus-4.7": { alias: "copilot-opus" }, "openai/gpt-5.5-pro": { alias: "old-pro" }, @@ -1770,10 +1771,9 @@ describe("legacy model compat migrate", () => { subagent?: { allowedModels?: string[] }; } )?.subagent?.allowedModels, - ).toEqual(["anthropic/claude-sonnet-4-6", "*"]); + ).toEqual(["anthropic/claude-haiku-4-5", "*"]); expect(res.config?.channels?.modelByChannel?.telegram?.["*"]).toBe("anthropic/claude-opus-4-7"); expectMigrationChangesToIncludeFragments(res.changes, [ - 'config.agents.defaults.imageModel from "anthropic/claude-haiku-4-5" to "anthropic/claude-sonnet-4-6"', 'config.agents.defaults.imageGenerationModel.primary from "github-copilot/claude-sonnet-4" to "github-copilot/claude-sonnet-4.6"', 'config.agents.defaults.imageGenerationModel.fallbacks.0 from "github-copilot/grok-code-fast-1" to "github-copilot/gpt-5.4-mini"', 'config.agents.defaults.musicGenerationModel from "vercel-ai-gateway/anthropic/claude-opus-4-5" to "vercel-ai-gateway/anthropic/claude-opus-4-6"', @@ -1800,7 +1800,6 @@ describe("legacy model compat migrate", () => { 'config.agents.defaults.models key from "openai/gpt-5.2-pro" to "openai/gpt-5.5-pro"', 'config.agents.defaults.models key from "github-copilot/gpt-5-mini" to "github-copilot/gpt-5.4-mini"', 'config.plugins.entries.lossless-claw.config.summaryModel from "anthropic/claude-3-5-sonnet" to "anthropic/claude-sonnet-4-6"', - 'config.plugins.entries.lossless-claw.subagent.allowedModels.0 from "anthropic/claude-haiku-4-5" to "anthropic/claude-sonnet-4-6"', 'config.channels.modelByChannel.telegram.* from "anthropic/claude-opus-4-5" to "anthropic/claude-opus-4-7"', ]); }); diff --git a/src/commands/doctor/shared/legacy-config-migrations.runtime.models.ts b/src/commands/doctor/shared/legacy-config-migrations.runtime.models.ts index 91a55962f4a..ed51e54c0b4 100644 --- a/src/commands/doctor/shared/legacy-config-migrations.runtime.models.ts +++ b/src/commands/doctor/shared/legacy-config-migrations.runtime.models.ts @@ -635,6 +635,13 @@ function upgradeOldClaudeToken( ) { return null; } + // claude-haiku-4-5 is a current production model and must not be migrated. + if ( + normalized.startsWith("claude-haiku-4-5") || + normalized.startsWith("claude-haiku-4.5") + ) { + return null; + } if ( normalized === "claude-opus-4" || hasAnyRetiredVersionPrefix(normalized, [ @@ -658,8 +665,6 @@ function upgradeOldClaudeToken( "claude-sonnet-4.1", "claude-sonnet-4-0", "claude-sonnet-4.0", - "claude-haiku-4-5", - "claude-haiku-4.5", ]) || /^claude-sonnet-4-20\d{6}/.test(normalized) ) { @@ -714,7 +719,6 @@ function upgradeOldClaudeToken( normalized === "sonnet-3.7" || normalized === "sonnet-3.5" || normalized === "sonnet-3" || - normalized === "haiku-4.5" || normalized === "haiku-3.5" || normalized === "haiku-3" ) {