From 7bbca051e46ed81dcc697cba631a13df3c2fbd08 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 07:42:07 +0000 Subject: [PATCH 1/2] test(server): custom Claude model named like a Claude Code alias reaches the API unchanged Fails on main: T3 passes "opus" to Claude Code, which expands its own built-in alias to claude-opus-5-5 because nothing overrides it. Repro for #22326. AI-assisted; verified with the tests and tools listed above. --- .../Adapters/ClaudeAdapterV2.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index eb6d4a8b5d0a8..478275db66abd 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -99,6 +99,7 @@ import { ClaudeExecutableFileCheck } from "../../provider/Drivers/ClaudeExecutab import { BUNDLED_CLAUDE_MODEL_CATALOG, type ClaudeModelCatalog, + scopeClaudeModelCatalog, } from "../../provider/ClaudeModelCatalog.ts"; import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; @@ -394,6 +395,30 @@ describe("ClaudeAdapterV2 runtime query policy", () => { }); }); + it("sends a custom model named like a Claude Code alias to the API unchanged", () => { + const options = ClaudeAdapterV2.makeClaudeQueryOptions({ + modelSelection: { instanceId: CLAUDE_TEST_MODEL_SELECTION.instanceId, model: "opus" }, + modelCatalog: scopeClaudeModelCatalog( + BUNDLED_CLAUDE_MODEL_CATALOG, + CUSTOM_MODEL_CLAUDE_SETTINGS.customModels, + ), + nativeThreadId: "native-custom-alias", + resume: false, + cwd: "/workspace", + settings: CUSTOM_MODEL_CLAUDE_SETTINGS, + }); + // Claude Code expands a bare alias to its own model unless ANTHROPIC_DEFAULT__MODEL overrides it. + const settingsEnv = + typeof options.settings === "object" && options.settings !== null + ? (options.settings as { env?: Record }).env + : undefined; + const aliasOverride = + settingsEnv?.ANTHROPIC_DEFAULT_OPUS_MODEL ?? options.env?.ANTHROPIC_DEFAULT_OPUS_MODEL; + const requestedModel = + options.model === "opus" ? (aliasOverride ?? "claude-opus-5-5") : options.model; + assert.equal(requestedModel, "opus"); + }); + it("projects AskUserQuestion input with question text as the answer key", () => { const preview = "\n **Proposed change**\n\n```ts\n\tconst enabled = true;\n```\n "; assert.deepEqual( From bf3c153112256a192fdfde0ea8c7876087adf595 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 07:46:12 +0000 Subject: [PATCH 2/2] fix(server): custom Claude models named like a Claude Code alias reach the API unchanged Claude Code expands a bare opus, sonnet, haiku or fable to its own built-in model, so a custom model with one of those names was swapped for the built-in one. Pin the alias to itself through the flag settings env (ANTHROPIC_DEFAULT__MODEL) when the selection sends one. Fixes pingdotgg/t3code#22326 AI-assisted; verified with the tests and tools listed above. --- .../Adapters/ClaudeAdapterV2.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 52782393470a4..f844c7ca3c183 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -1914,6 +1914,8 @@ const subagentDeviceInstructions: HookCallback = () => }, }); +const CLAUDE_CODE_MODEL_ALIASES = new Set(["opus", "sonnet", "haiku", "fable"]); + export function makeClaudeQueryOptions(input: { readonly modelSelection: ModelSelection; readonly modelCatalog?: ClaudeModelCatalog; @@ -1989,13 +1991,28 @@ export function makeClaudeQueryOptions(input: { ...(typeof querySettings === "object" && querySettings !== null ? querySettings : {}), autoCompactWindow: Number(input.settings.autoCompactWindow), } as ClaudeSdkSettings); + // Claude Code expands a bare alias to its own model, so a custom model with an alias's name + // pins the alias to itself. + const aliasOverrideEnv = CLAUDE_CODE_MODEL_ALIASES.has(compiledSelection.apiModelId) + ? { + [`ANTHROPIC_DEFAULT_${compiledSelection.apiModelId.toUpperCase()}_MODEL`]: + compiledSelection.apiModelId, + } + : undefined; // A settings file path cannot take extra keys, so it goes through unchanged. const effectiveQuerySettings = - input.toolSearchOverride === undefined || typeof compactQuerySettings === "string" + (input.toolSearchOverride === undefined && aliasOverrideEnv === undefined) || + typeof compactQuerySettings === "string" ? compactQuerySettings : ({ ...compactQuerySettings, - env: { ...compactQuerySettings?.env, ENABLE_TOOL_SEARCH: input.toolSearchOverride }, + env: { + ...compactQuerySettings?.env, + ...aliasOverrideEnv, + ...(input.toolSearchOverride === undefined + ? {} + : { ENABLE_TOOL_SEARCH: input.toolSearchOverride }), + }, } as ClaudeSdkSettings); const deviceInstructions = input.mcpServers !== undefined && input.deviceToolsAvailable === true; const options: ClaudeAgentSdkQueryOptions = {