[BUG] OpenRouter model names (dot notation) not recognized by getCanonicalName, causing modelSupports1M=false and capability degradation

Resolved 💬 6 comments Opened Apr 13, 2026 by easyfan Closed Jun 11, 2026

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

  1. Configure CC to use OpenRouter with ANTHROPIC_BASE_URL=https://openrouter.ai/api
  2. Set ANTHROPIC_DEFAULT_SONNET_MODEL=anthropic/claude-sonnet-4.6
  3. Observe that modelSupports1M() returns false for this model
  4. 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

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗