[BUG] OpenRouter model names (dot notation) not recognized by getCanonicalName, causing modelSupports1M=false and capability degradation
Summary
Claude Code's firstPartyNameToCanonical() and related functions use dash notation (claude-opus-4-6) to match model names, but OpenRouter's official model IDs use dot notation (anthropic/claude-opus-4.6). This mismatch causes silent capability degradation for all OpenRouter users.
Environment
- Claude Code version: 2.1.104
- API provider: OpenRouter (
ANTHROPIC_BASE_URL=https://openrouter.ai/api) - Model IDs configured:
anthropic/claude-opus-4.6,anthropic/claude-sonnet-4.6,anthropic/claude-haiku-4.5
Root Cause
In src/utils/model/model.ts, firstPartyNameToCanonical() checks:
if (name.includes('claude-opus-4-6')) { return 'claude-opus-4-6' }
if (name.includes('claude-sonnet-4-6')) { return 'claude-sonnet-4-6' }
OpenRouter's official model IDs (verified at https://openrouter.ai/anthropic/claude-opus-4.6) are:
anthropic/claude-opus-4.6(dot:4.6)anthropic/claude-sonnet-4.6(dot:4.6)
Since 'anthropic/claude-opus-4.6'.includes('claude-opus-4-6') → false, the function falls back to regex matching and returns 'claude-opus' or 'claude-sonnet' (no version suffix).
CC has zero OpenRouter-specific code — the naming convention mismatch is entirely on the CC side.
Impact
Three downstream functions silently return wrong values:
1. modelSupports1M() → false instead of true
// src/utils/context.ts
const canonical = getCanonicalName(model)
return canonical.includes('claude-sonnet-4') || canonical.includes('opus-4-6')
// 'claude-sonnet'.includes('claude-sonnet-4') → false
// 'claude-opus'.includes('opus-4-6') → false
Effect: CC never activates 1M context window for OpenRouter users, even when the model supports it. This degrades context management, triggers earlier compaction, and makes the model appear less capable in long-context tasks.
2. getModelMaxOutputTokens() upperLimit: 64K instead of 128K
} else if (m.includes('sonnet-4-6')) {
defaultTokens = 32_000
upperLimit = 128_000 // never reached for OpenRouter
}
// Falls to: upperLimit = MAX_OUTPUT_TOKENS_UPPER_LIMIT = 64_000
Effect: Upper bound for output tokens is 64K instead of 128K.
3. getSonnet1mExpTreatmentEnabled() → always false
Minor but affects experimental features gated on sonnet 4.6 detection.
How to Reproduce
- Configure CC to use OpenRouter with
ANTHROPIC_BASE_URL=https://openrouter.ai/api - Set
ANTHROPIC_DEFAULT_SONNET_MODEL=anthropic/claude-sonnet-4.6 - Observe that
modelSupports1M()returnsfalsefor this model - Long-context tasks behave as if using a model without 1M context support
Suggested Fix
Add dot-notation patterns to firstPartyNameToCanonical():
// Handle both dash notation (1P) and dot notation (OpenRouter)
if (name.includes('claude-opus-4-6') || name.includes('claude-opus-4.6')) {
return 'claude-opus-4-6'
}
if (name.includes('claude-sonnet-4-6') || name.includes('claude-sonnet-4.6')) {
return 'claude-sonnet-4-6'
}
if (name.includes('claude-haiku-4-5') || name.includes('claude-haiku-4.5')) {
return 'claude-haiku-4-5'
}
Or alternatively, normalize dot-to-dash in the function before pattern matching:
export function firstPartyNameToCanonical(name: ModelName): ModelShortName {
name = name.toLowerCase().replace(/(\d+)\.(\d+)/g, '$1-$2') // normalize 4.6 → 4-6
// ... existing checks unchanged
}
Additional Context
Discovered during investigation of a subagent tool-call failure on OpenRouter. The naming mismatch is silent — no warning or error is thrown, making it very hard to diagnose. Users experiencing "weaker" model performance on OpenRouter vs direct Anthropic API may be hitting this issue.
Generated with Claude Code
via Happy
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗