From a3fcc3cb7fa1d32945841be978dfc98c23e64733 Mon Sep 17 00:00:00 2001 From: Nikolas Syring Date: Thu, 18 Dec 2025 09:24:51 +0100 Subject: [PATCH] fix: Simplify context display to use Claude's reported value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove broken token-based context calculation. The StatusBar now only shows context remaining when Claude reports "Context left until auto-compact: X%" - which happens when context is actually getting low. This eliminates confusing/incorrect percentages and only shows relevant information when needed. TODO: Test this in a long session to verify the context warning appears when Claude reports it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/src/components/StatusBar.jsx | 48 +++++----------------- frontend/src/hooks/useClaudeSession.js | 56 +++++++++++++++++++++----- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/frontend/src/components/StatusBar.jsx b/frontend/src/components/StatusBar.jsx index 8007de2..9765531 100644 --- a/frontend/src/components/StatusBar.jsx +++ b/frontend/src/components/StatusBar.jsx @@ -23,6 +23,7 @@ export function StatusBar({ sessionStats, isProcessing, connected, permissionMod cacheCreationTokens = 0, numTurns = 0, contextWindow = 200000, + contextLeftPercent = null, // From Claude's "Context left until auto-compact: X%" message isCompacting = false, } = sessionStats || {}; @@ -30,12 +31,9 @@ export function StatusBar({ sessionStats, isProcessing, connected, permissionMod const currentMode = PERMISSION_MODES.find(m => m.value === permissionMode) || PERMISSION_MODES[0]; const ModeIcon = currentMode.icon; - // Calculate context usage - // inputTokens from modelUsage represents tokens in current context (including cache reads) - // contextWindow is the model's max context size (e.g., 200000 for opus) - const contextUsed = inputTokens + outputTokens; - const contextPercent = contextWindow > 0 ? Math.min(100, (contextUsed / contextWindow) * 100) : 0; - const contextRemaining = Math.max(0, 100 - contextPercent); + // Context remaining: Use Claude's reported value if available, otherwise don't show + // Claude only reports this when context is getting low (< ~15%) + const contextRemaining = contextLeftPercent; // Format cost const formatCost = (cost) => { @@ -139,43 +137,19 @@ export function StatusBar({ sessionStats, isProcessing, connected, permissionMod {/* Right side: Token usage */}
- {/* Token counts */} - {contextUsed > 0 && ( -
- - In: - {formatTokens(inputTokens)} - - - Out: - {formatTokens(outputTokens)} - - {cacheReadTokens > 0 && ( - - - {formatTokens(cacheReadTokens)} - - )} -
- )} - - {/* Context status - shows remaining context percentage */} -
- Context: - {contextUsed > 0 ? ( + {/* Context status - only shown when Claude reports it (context getting low) */} + {contextRemaining !== null && ( +
+ Context: - {contextRemaining <= 30 - ? `${contextRemaining.toFixed(0)}% left` - : `${contextPercent.toFixed(0)}% used`} + {contextRemaining}% left - ) : ( - ok - )} -
+
+ )}
diff --git a/frontend/src/hooks/useClaudeSession.js b/frontend/src/hooks/useClaudeSession.js index 59095d6..d2eee62 100644 --- a/frontend/src/hooks/useClaudeSession.js +++ b/frontend/src/hooks/useClaudeSession.js @@ -33,6 +33,7 @@ export function useClaudeSession() { cacheCreationTokens: 0, numTurns: 0, contextWindow: 200000, // Default, updated from modelUsage + contextLeftPercent: null, // From Claude's "Context left until auto-compact: X%" message isCompacting: false, }); @@ -371,18 +372,38 @@ export function useClaudeSession() { || Object.keys(modelUsage)[0]; const primaryUsage = primaryModel ? modelUsage[primaryModel] : null; - // Calculate effective input tokens (what's actually in context) - // This is: new input + cache read (cache creation doesn't count towards context limit) - const effectiveInput = (event.usage?.input_tokens || 0) + - (event.usage?.cache_read_input_tokens || 0); + // Context calculation: Use top-level usage (aggregated) as primary source + // Per GitHub issue anthropics/claude-agent-sdk-typescript#66: + // Cache tokens DON'T count toward the 200K context window - they're handled separately + // Only input_tokens + output_tokens count toward the context window + // Cache tokens can appear in the millions because they're cumulative across the session + const usage = event.usage || {}; + + // Prefer top-level usage (aggregated), fallback to primaryUsage (per-model) + const inputTokens = usage.input_tokens ?? primaryUsage?.inputTokens ?? 0; + const cacheRead = usage.cache_read_input_tokens ?? primaryUsage?.cacheReadInputTokens ?? 0; + const cacheCreation = usage.cache_creation_input_tokens ?? primaryUsage?.cacheCreationInputTokens ?? 0; + const outputTokens = usage.output_tokens ?? primaryUsage?.outputTokens ?? 0; + + // Context = input + output tokens ONLY (cache tokens are separate) + const totalContextUsed = inputTokens + outputTokens; + + console.log('Result event stats:', { + inputTokens, + cacheRead, + cacheCreation, + totalContextUsed, + outputTokens, + contextWindow: primaryUsage?.contextWindow + }); setSessionStats(prev => ({ totalCost: event.total_cost_usd ?? prev.totalCost, - // Use modelUsage for accurate per-turn counts, fallback to usage - inputTokens: primaryUsage?.inputTokens ?? effectiveInput ?? prev.inputTokens, - outputTokens: primaryUsage?.outputTokens ?? event.usage?.output_tokens ?? prev.outputTokens, - cacheReadTokens: primaryUsage?.cacheReadInputTokens ?? event.usage?.cache_read_input_tokens ?? prev.cacheReadTokens, - cacheCreationTokens: primaryUsage?.cacheCreationInputTokens ?? event.usage?.cache_creation_input_tokens ?? prev.cacheCreationTokens, + // Context = input + output tokens only (cache tokens don't count toward window) + inputTokens: totalContextUsed, + outputTokens: outputTokens, + cacheReadTokens: cacheRead, + cacheCreationTokens: cacheCreation, numTurns: event.num_turns ?? prev.numTurns, contextWindow: primaryUsage?.contextWindow ?? prev.contextWindow, isCompacting: false, @@ -390,9 +411,22 @@ export function useClaudeSession() { } } else if (event.type === 'system' && event.subtype === 'result') { setIsProcessing(false); - } else if (event.type === 'system' && event.message?.includes?.('compact')) { + } else if (event.type === 'system' && event.message) { + // Parse "Context left until auto-compact: X%" message + const contextMatch = event.message.match(/Context left until auto-compact:\s*(\d+)%/i); + if (contextMatch) { + const contextLeft = parseInt(contextMatch[1], 10); + console.log('Context left from Claude:', contextLeft + '%'); + setSessionStats(prev => ({ + ...prev, + contextLeftPercent: contextLeft, + isCompacting: false, + })); + } // Detect compacting - setSessionStats(prev => ({ ...prev, isCompacting: true })); + if (event.message.includes('compact')) { + setSessionStats(prev => ({ ...prev, isCompacting: true })); + } } }, []);