Skip to content

Commit 64429e2

Browse files
committed
fix(anthropic): stop migrating current claude-haiku-4-5 to sonnet
claude-haiku-4-5 is a current production model but was classified as retired in both the Claude model-ref resolver and the doctor config migration, so doctor --fix and the bare haiku alias rewrote it to claude-sonnet-4-6, overriding explicit user model choices (Fixes #87680). Remove claude-haiku-4-5 and claude-haiku-4.5 from the retired upgrade lists in both mirrors, add an explicit current-model guard, and repoint the haiku family alias to claude-haiku-4-5. Genuinely retired Claude 3 Haiku still upgrades to sonnet.
1 parent e67ff0c commit 64429e2

4 files changed

Lines changed: 37 additions & 11 deletions

File tree

extensions/anthropic/claude-model-refs.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_MODEL_ALIASES } from "./cli-constants
44
const DEFAULT_CLAUDE_MODEL_BY_FAMILY: Record<string, string> = {
55
opus: "claude-opus-4-7",
66
sonnet: "claude-sonnet-4-6",
7-
haiku: "claude-sonnet-4-6",
7+
haiku: "claude-haiku-4-5",
88
};
99

1010
export type ClaudeCliAnthropicModelRefs = {
@@ -117,6 +117,10 @@ function upgradeOldClaudeModelId(normalized: string): string | null {
117117
if (normalized.startsWith("claude-sonnet-4-6") || normalized.startsWith("claude-sonnet-4.6")) {
118118
return null;
119119
}
120+
// claude-haiku-4-5 is a current production model and must not be migrated.
121+
if (normalized.startsWith("claude-haiku-4-5") || normalized.startsWith("claude-haiku-4.5")) {
122+
return null;
123+
}
120124
if (
121125
normalized === "claude-opus-4" ||
122126
hasAnyRetiredVersionPrefix(normalized, [
@@ -140,8 +144,6 @@ function upgradeOldClaudeModelId(normalized: string): string | null {
140144
"claude-sonnet-4.1",
141145
"claude-sonnet-4-0",
142146
"claude-sonnet-4.0",
143-
"claude-haiku-4-5",
144-
"claude-haiku-4.5",
145147
]) ||
146148
/^claude-sonnet-4-20\d{6}/.test(normalized)
147149
) {
@@ -172,7 +174,6 @@ function upgradeOldClaudeModelId(normalized: string): string | null {
172174
normalized === "sonnet-3.7" ||
173175
normalized === "sonnet-3.5" ||
174176
normalized === "sonnet-3" ||
175-
normalized === "haiku-4.5" ||
176177
normalized === "haiku-3.5" ||
177178
normalized === "haiku-3"
178179
) {

extensions/anthropic/cli-migration.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,28 @@ describe("anthropic Claude model refs", () => {
5555
expect(resolveKnownAnthropicModelRef("anthropic/claude-sonnet-4-7")).toBe(
5656
"anthropic/claude-sonnet-4-7",
5757
);
58+
expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4-5")).toBe(
59+
"anthropic/claude-haiku-4-5",
60+
);
61+
});
62+
63+
it("preserves the current claude-haiku-4-5 model and its bare alias", () => {
64+
// claude-haiku-4-5 is a current production model (not retired), so neither
65+
// its full ref, its dotted variant, nor the bare "haiku" family alias must
66+
// be rewritten to sonnet.
67+
expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4-5")).toBe(
68+
"anthropic/claude-haiku-4-5",
69+
);
70+
expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4.5")).toBe(
71+
"anthropic/claude-haiku-4.5",
72+
);
73+
expect(resolveKnownAnthropicModelRef("anthropic/claude-haiku-4-5@anthropic:work")).toBe(
74+
"anthropic/claude-haiku-4-5@anthropic:work",
75+
);
76+
// Genuinely retired Claude 3 Haiku still upgrades to the current sonnet.
77+
expect(resolveKnownAnthropicModelRef("anthropic/claude-3-5-haiku-20241022")).toBe(
78+
"anthropic/claude-sonnet-4-6",
79+
);
5880
});
5981
});
6082

src/commands/doctor/shared/legacy-config-migrate.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,7 +1707,7 @@ describe("legacy model compat migrate", () => {
17071707
},
17081708
});
17091709

1710-
expect(res.config?.agents?.defaults?.imageModel).toBe("anthropic/claude-sonnet-4-6");
1710+
expect(res.config?.agents?.defaults?.imageModel).toBe("anthropic/claude-haiku-4-5");
17111711
expect(res.config?.agents?.defaults?.imageGenerationModel).toEqual({
17121712
primary: "github-copilot/claude-sonnet-4.6",
17131713
fallbacks: ["github-copilot/gpt-5.4-mini"],
@@ -1751,6 +1751,7 @@ describe("legacy model compat migrate", () => {
17511751
});
17521752
expect(res.config?.agents?.defaults?.workspace).toBe("/tmp/claude-3-sonnet");
17531753
expect(res.config?.agents?.defaults?.models).toEqual({
1754+
"anthropic/claude-haiku-4-5": { alias: "haiku" },
17541755
"anthropic/claude-sonnet-4-6": { alias: "current-sonnet" },
17551756
"github-copilot/claude-opus-4.7": { alias: "copilot-opus" },
17561757
"openai/gpt-5.5-pro": { alias: "old-pro" },
@@ -1770,10 +1771,9 @@ describe("legacy model compat migrate", () => {
17701771
subagent?: { allowedModels?: string[] };
17711772
}
17721773
)?.subagent?.allowedModels,
1773-
).toEqual(["anthropic/claude-sonnet-4-6", "*"]);
1774+
).toEqual(["anthropic/claude-haiku-4-5", "*"]);
17741775
expect(res.config?.channels?.modelByChannel?.telegram?.["*"]).toBe("anthropic/claude-opus-4-7");
17751776
expectMigrationChangesToIncludeFragments(res.changes, [
1776-
'config.agents.defaults.imageModel from "anthropic/claude-haiku-4-5" to "anthropic/claude-sonnet-4-6"',
17771777
'config.agents.defaults.imageGenerationModel.primary from "github-copilot/claude-sonnet-4" to "github-copilot/claude-sonnet-4.6"',
17781778
'config.agents.defaults.imageGenerationModel.fallbacks.0 from "github-copilot/grok-code-fast-1" to "github-copilot/gpt-5.4-mini"',
17791779
'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", () => {
18001800
'config.agents.defaults.models key from "openai/gpt-5.2-pro" to "openai/gpt-5.5-pro"',
18011801
'config.agents.defaults.models key from "github-copilot/gpt-5-mini" to "github-copilot/gpt-5.4-mini"',
18021802
'config.plugins.entries.lossless-claw.config.summaryModel from "anthropic/claude-3-5-sonnet" to "anthropic/claude-sonnet-4-6"',
1803-
'config.plugins.entries.lossless-claw.subagent.allowedModels.0 from "anthropic/claude-haiku-4-5" to "anthropic/claude-sonnet-4-6"',
18041803
'config.channels.modelByChannel.telegram.* from "anthropic/claude-opus-4-5" to "anthropic/claude-opus-4-7"',
18051804
]);
18061805
});

src/commands/doctor/shared/legacy-config-migrations.runtime.models.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,13 @@ function upgradeOldClaudeToken(
635635
) {
636636
return null;
637637
}
638+
// claude-haiku-4-5 is a current production model and must not be migrated.
639+
if (
640+
normalized.startsWith("claude-haiku-4-5") ||
641+
normalized.startsWith("claude-haiku-4.5")
642+
) {
643+
return null;
644+
}
638645
if (
639646
normalized === "claude-opus-4" ||
640647
hasAnyRetiredVersionPrefix(normalized, [
@@ -658,8 +665,6 @@ function upgradeOldClaudeToken(
658665
"claude-sonnet-4.1",
659666
"claude-sonnet-4-0",
660667
"claude-sonnet-4.0",
661-
"claude-haiku-4-5",
662-
"claude-haiku-4.5",
663668
]) ||
664669
/^claude-sonnet-4-20\d{6}/.test(normalized)
665670
) {
@@ -714,7 +719,6 @@ function upgradeOldClaudeToken(
714719
normalized === "sonnet-3.7" ||
715720
normalized === "sonnet-3.5" ||
716721
normalized === "sonnet-3" ||
717-
normalized === "haiku-4.5" ||
718722
normalized === "haiku-3.5" ||
719723
normalized === "haiku-3"
720724
) {

0 commit comments

Comments
 (0)