From 7c154c6659ea876b8dfcbfb3e96ce5095cef365c Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 11 Apr 2026 13:06:24 +0900 Subject: [PATCH 1/2] fix(anthropic-effort): skip effort injection for Haiku models (#3308) Haiku models do not support the effort parameter and return API errors when it is passed. The hook now explicitly checks for Haiku model patterns and skips effort injection, preventing silent title generation failures. Added EFFORT_UNSUPPORTED_PATTERN and isEffortUnsupportedModel() to detect and skip Haiku models. Fixes #3308 --- src/hooks/anthropic-effort/hook.ts | 7 +++++++ src/hooks/anthropic-effort/index.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/hooks/anthropic-effort/hook.ts b/src/hooks/anthropic-effort/hook.ts index 76fba5245..fb5a9a209 100644 --- a/src/hooks/anthropic-effort/hook.ts +++ b/src/hooks/anthropic-effort/hook.ts @@ -1,6 +1,7 @@ import { log, normalizeModelID } from "../../shared" const OPUS_PATTERN = /claude-.*opus/i +const EFFORT_UNSUPPORTED_PATTERN = /claude-.*haiku/i const INTERNAL_SKIP_AGENTS = new Set(["title", "summary", "compaction"]) function isClaudeProvider(providerID: string, modelID: string): boolean { @@ -14,6 +15,11 @@ function isOpusModel(modelID: string): boolean { return OPUS_PATTERN.test(normalized) } +function isEffortUnsupportedModel(modelID: string): boolean { + const normalized = normalizeModelID(modelID) + return EFFORT_UNSUPPORTED_PATTERN.test(normalized) +} + function shouldSkipForInternalAgent(agentName: string | undefined): boolean { if (!agentName) return false return INTERNAL_SKIP_AGENTS.has(agentName.trim().toLowerCase()) @@ -60,6 +66,7 @@ export function createAnthropicEffortHook() { if (!isClaudeProvider(model.providerID, model.modelID)) return if (shouldSkipForInternalAgent(agent?.name)) return if (output.options.effort !== undefined) return + if (isEffortUnsupportedModel(model.modelID)) return const opus = isOpusModel(model.modelID) const clamped = clampVariant(message.variant, opus) diff --git a/src/hooks/anthropic-effort/index.test.ts b/src/hooks/anthropic-effort/index.test.ts index 056ff0a28..5ca923cd2 100644 --- a/src/hooks/anthropic-effort/index.test.ts +++ b/src/hooks/anthropic-effort/index.test.ts @@ -147,6 +147,30 @@ describe("createAnthropicEffortHook", () => { expect(output.options.effort).toBeUndefined() }) + + describe("#given haiku models (effort unsupported)", () => { + const haikuModels = [ + "claude-haiku-4-5", + "claude-haiku-4.6", + "claude-haiku", + "claude-haiku-20240307", + ] + + for (const modelID of haikuModels) { + it(`skips effort injection for ${modelID}`, async () => { + // given + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ modelID }) + + // when + await hook["chat.params"](input, output) + + // then + expect(output.options.effort).toBeUndefined() + expect(input.message.variant).toBe("max") + }) + } + }) }) describe("existing options", () => { From 522f8aab6c5339818bc20da8f5264ddc9cb53963 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 11 Apr 2026 13:44:35 +0900 Subject: [PATCH 2/2] fix: move Haiku check before variant check for reliability Addresses cubic-dev-ai review feedback. The Haiku early-return now runs immediately after model validation, ensuring it's always reached regardless of any upstream variant normalization. --- src/hooks/anthropic-effort/hook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/anthropic-effort/hook.ts b/src/hooks/anthropic-effort/hook.ts index fb5a9a209..31b18c7e6 100644 --- a/src/hooks/anthropic-effort/hook.ts +++ b/src/hooks/anthropic-effort/hook.ts @@ -62,11 +62,11 @@ export function createAnthropicEffortHook() { ): Promise => { const { agent, model, message } = input if (!model?.modelID || !model?.providerID) return + if (isEffortUnsupportedModel(model.modelID)) return if (message.variant !== "max") return if (!isClaudeProvider(model.providerID, model.modelID)) return if (shouldSkipForInternalAgent(agent?.name)) return if (output.options.effort !== undefined) return - if (isEffortUnsupportedModel(model.modelID)) return const opus = isOpusModel(model.modelID) const clamped = clampVariant(message.variant, opus)