[BUG] 'CLAUDE_CODE_MAX_OUTPUT_TOKENS' incorrectly validates Sonnet models against 32k limit instead of 64k

Resolved 💬 10 comments Opened Jul 26, 2025 by salah9003 Closed Sep 15, 2025

Environment

  • Platform (select one):
  • [x] Anthropic API
  • [ ] AWS Bedrock
  • [ ] Google Vertex AI
  • [ ] Other: <!-- specify -->
  • Claude CLI version: 1.0.61
  • Operating System: Windows 11 with WSL2
  • Terminal: Windows Powershell (WSL2)

Bug Description

The oU0 function in Claude Code's cli.js incorrectly enforces a 32,000 token limit for all models, when Sonnet models actually support up to 64,000 tokens. The validation function uses a hardcoded 32,000 limit regardless of model type.

Steps to Reproduce

  1. Create or update ~/.claude/settings.json with:
{
  "env": {
    "ANTHROPIC_CUSTOM_HEADERS": "anthropic-beta: interleaved-thinking-2025-05-14",
    "MAX_THINKING_TOKENS": "100000",
    "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "64000"
  },
  "model": "sonnet"
}
  1. Launch Claude Code in interactive mode by executing claude
  2. The following error is printed in terminal: ERROR Invalid env var CLAUDE_CODE_MAX_OUTPUT_TOKENS: 64000

Expected Behavior

The validation should respect model-specific token limits:

  • Haiku models: Allow up to 8,192 tokens
  • Sonnet models: Allow up to 64,000 tokens
  • Opus models: Allow up to 32,000 tokens

Actual Behavior

Any value above 32,000 triggers an error regardless of model type:

ERROR Invalid env var CLAUDE_CODE_MAX_OUTPUT_TOKENS: 50000

Additional Context

I've identified the exact fix needed in the minified cli.js file. The oU0 function needs to check model-specific limits.

Current implementation:

function oU0(A){if(A.includes("3-5"))return 8192;if(A.includes("haiku"))return 8192;let B=process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS;if(B){let Q=parseInt(B,10);if(!isNaN(Q)&&Q>0&&Q<=32000)return Q;else throw new Error(`Invalid env var CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${B}`)}return 32000}

Fixed implementation:

function oU0(A){let maxTokens=A.includes("haiku")?8192:(A.includes("3-5")||A.includes("sonnet"))?64000:32000;let B=process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS;if(B){let Q=parseInt(B,10);if(!isNaN(Q)&&Q>0&&Q<=maxTokens)return Q;else throw new Error(`Invalid env var CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${B}. Must be between 1 and ${maxTokens} for model ${A}`)}return A.includes("haiku")?8192:32000}

I've tested this fix locally and confirmed:

  • Haiku models correctly limited to 8,192 tokens
  • Sonnet models now accept up to 64,000 tokens
  • Opus models correctly limited to 32,000 tokens
  • Error messages include model-specific limits

This fix maintains backward compatibility while enabling Sonnet users to access the full 64k token capacity of their models.

---

EDIT: Additional Issue - Model Persistence in settings.json

Problem Description

When using /model in Claude CLI to switch models at runtime, the settings.json file's model field was being permanently modified:

  • Switching from default to another model would add/update the model field
  • Switching back to default (sonnet) would completely remove the model field from settings.json
  • This made it impossible to temporarily switch models without affecting the persistent configuration

Root Cause

The issue was in the em function (line 356531 in the prettified cli.js), which is an onChangeAppState handler that monitors state changes. When mainLoopModel changed, it was calling:

m3("userSettings", { model: void 0 })  // When switching to default
m3("userSettings", { model: A.mainLoopModel })  // When switching to other models

The m3 function persists settings to the user settings file, causing unwanted persistence of runtime model changes.

Solution

Modified the em function to only update runtime state without persisting to settings:

Before:

function em({ newState: A, oldState: B }) {
  if (B !== null && A.mainLoopModel !== B.mainLoopModel && A.mainLoopModel === null)
    (m3("userSettings", { model: void 0 }), HA1(null));
  if (B !== null && A.mainLoopModel !== B.mainLoopModel && A.mainLoopModel !== null)
    (m3("userSettings", { model: A.mainLoopModel }), HA1(A.mainLoopModel));
  // ... rest of function
}

After:

function em({ newState: A, oldState: B }) {
  if (B !== null && A.mainLoopModel !== B.mainLoopModel && A.mainLoopModel === null)
    HA1(null); // Removed m3("userSettings", { model: void 0 })
  if (B !== null && A.mainLoopModel !== B.mainLoopModel && A.mainLoopModel !== null)
    HA1(A.mainLoopModel); // Removed m3("userSettings", { model: A.mainLoopModel })
  // ... rest of function
}

Testing

Confirmed that after applying this fix:

  • Using /model to switch models only affects the current session
  • The settings.json file remains unchanged
  • Default model setting is preserved across sessions
  • Runtime model switching still works as expected

This fix ensures that /model behaves as a session-only command without side effects on persistent configuration.

View original on GitHub ↗

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