-
-
Notifications
You must be signed in to change notification settings - Fork 317
feat(ccusage): support context_window field from Claude Code statusline hook #749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ne hook Claude Code now provides context_window data in its statusline hook JSON, containing total_input_tokens, total_output_tokens, and context_window_size. This change: - Adds context_window field to statuslineHookJsonSchema in _types.ts - Updates statusline command to prefer context_window data when available - Falls back to existing transcript-based calculation when not provided - Refactors context info formatting into reusable helper function - Uses Result type pattern for consistent functional error handling The context_window data from Claude Code is more accurate than parsing the transcript file, as it reflects the actual token counts used by the API.
|
Caution Review failedThe pull request is closed. WalkthroughThis change adds an optional Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20–30 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | 555f414 | Dec 11 2025, 09:22 PM |
Add note about Claude Code's context_window data being used for accurate token counts, with link to official documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/ccusage/src/_types.ts (1)
176-180: statuslineHookJsonSchema: context_window shape looks right; consider basic numeric validationThe new
context_windowfield matches the Claude Code hook structure and is correctly marked optional. To harden validation a bit (and prevent negative / zero edge cases from propagating), you could optionally enforce non‑negative tokens and a positive window size:- context_window: v.optional(v.object({ - total_input_tokens: v.number(), - total_output_tokens: v.optional(v.number()), - context_window_size: v.number(), - })), + context_window: v.optional(v.object({ + total_input_tokens: v.pipe( + v.number(), + v.minValue(0, 'total_input_tokens must be >= 0'), + ), + total_output_tokens: v.optional(v.pipe( + v.number(), + v.minValue(0, 'total_output_tokens must be >= 0'), + )), + context_window_size: v.pipe( + v.number(), + v.minValue(1, 'context_window_size must be >= 1'), + ), + })),Not required for correctness given Claude Code’s constraints, but it provides clearer guarantees at the boundary.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/ccusage/src/_types.ts(1 hunks)apps/ccusage/src/commands/statusline.ts(1 hunks)apps/ccusage/test/statusline-test-opus4.json(1 hunks)apps/ccusage/test/statusline-test-sonnet4.json(1 hunks)apps/ccusage/test/statusline-test-sonnet41.json(1 hunks)apps/ccusage/test/statusline-test.json(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/ccusage/src/**/*.ts
📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)
apps/ccusage/src/**/*.ts: Write tests in-source usingif (import.meta.vitest != null)blocks instead of separate test files
Use Vitest globals (describe,it,expect) without imports in test blocks
In tests, use current Claude 4 models (sonnet-4, opus-4)
Usefs-fixturewithcreateFixture()to simulate Claude data in tests
Only export symbols that are actually used by other modules
Do not use console.log; use the logger utilities fromsrc/logger.tsinstead
Files:
apps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.ts
apps/ccusage/**/*.ts
📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)
apps/ccusage/**/*.ts: NEVER useawait import()dynamic imports anywhere (especially in tests)
Prefer@praha/byethrowResult type for error handling instead of try-catch
Use.tsextensions for local imports (e.g.,import { foo } from './utils.ts')
Files:
apps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use ESLint for linting and formatting with tab indentation and double quotes
No console.log allowed except where explicitly disabled with eslint-disable; use logger.ts instead
Use file paths with Node.js path utilities for cross-platform compatibility
Use variables starting with lowercase (camelCase) for variable names
Can use UPPER_SNAKE_CASE for constants
Files:
apps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.ts
**/*.ts{,x}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict mode and bundler module resolution
Files:
apps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Use.tsextensions for local file imports (e.g.,import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
UseResult.try()for wrapping operations that may throw (JSON parsing, etc.)
UseResult.isFailure()for checking errors (more readable than!Result.isSuccess())
Use early return pattern (if (Result.isFailure(result)) continue;) instead of ternary operators when checking Results
Keep traditional try-catch only for file I/O with complex error handling or legacy code that's hard to refactor
Always useResult.isFailure()andResult.isSuccess()type guards for better code clarity
Use uppercase (PascalCase) for type names
Only export constants, functions, and types that are actually used by other modules - internal constants used only within the same file should NOT be exported
In-source testing pattern: write tests directly in source files usingif (import.meta.vitest != null)blocks
CRITICAL: DO NOT useawait import()dynamic imports anywhere in the codebase - this causes tree-shaking issues
CRITICAL: Never use dynamic imports withawait import()in vitest test blocks - this is particularly problematic for test execution
Vitest globals (describe,it,expect) are enabled and available without imports since globals are configured
Create mock data usingfs-fixturewithcreateFixture()for Claude data directory simulation in tests
All test files must use current Claude 4 models (claude-sonnet-4-20250514, claude-opus-4-20250514), not outdated Claude 3 models
Model names in tests must exactly match LiteLLM's pricing database entries
Files:
apps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.ts
**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Claude model naming convention:
claude-{model-type}-{generation}-{date}(e.g.,claude-sonnet-4-20250514, NOTclaude-4-sonnet-20250514)
Files:
apps/ccusage/src/commands/statusline.tsapps/ccusage/test/statusline-test-sonnet41.jsonapps/ccusage/test/statusline-test.jsonapps/ccusage/src/_types.tsapps/ccusage/test/statusline-test-sonnet4.jsonapps/ccusage/test/statusline-test-opus4.json
🧠 Learnings (1)
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Token fields semantics: input_tokens, cached_input_tokens, output_tokens, reasoning_output_tokens (informational), and total_tokens; for legacy entries recompute total as input + output (reasoning already included in output)
Applied to files:
apps/ccusage/test/statusline-test-sonnet41.json
🧬 Code graph analysis (1)
apps/ccusage/src/commands/statusline.ts (1)
apps/ccusage/src/data-loader.ts (1)
calculateContextTokens(1235-1311)
🪛 GitHub Actions: CI
apps/ccusage/src/commands/statusline.ts
[error] 433-433: typos: 'colour' should be 'color'. Step 'nix develop --command typos --config ./typos.toml' failed with exit code 2.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Workers Builds: ccusage-guide
🔇 Additional comments (4)
apps/ccusage/test/statusline-test-opus4.json (1)
24-28: Context window fixture for opus-4 looks consistent with schemaThe new
context_windowblock matches theStatuslineHookJsonschema and uses sensible values for an opus-4 200k context window. No further changes needed here.apps/ccusage/test/statusline-test-sonnet4.json (1)
24-28: Sonnet 4 context_window fixture aligns with new hook schemaThe
context_windowobject shape and token/window values look correct and consistent with the updatedstatuslineHookJsonSchema.apps/ccusage/test/statusline-test-sonnet41.json (1)
24-28: Sonnet 4.1 fixture now includes context_window; looks goodThe added
context_windowsection matches the new schema and gives realistic token usage for a 200k window Sonnet 4.1 session.apps/ccusage/test/statusline-test.json (1)
24-28: statusline-test context_window fixture matches runtime expectationsThe new
context_windowblock has the correct shape and realistic token counts for a 200k context window; it should exercise the new preferred data path instatusline.
| // Helper function to format context info with colour coding | ||
| const formatContextInfo = (inputTokens: number, contextLimit: number): string => { | ||
| const percentage = Math.round((inputTokens / contextLimit) * 100); | ||
| const color = percentage < ctx.values.contextLowThreshold | ||
| ? pc.green | ||
| : percentage < ctx.values.contextMediumThreshold | ||
| ? pc.yellow | ||
| : pc.red; | ||
| const coloredPercentage = color(`${percentage}%`); | ||
| const tokenDisplay = inputTokens.toLocaleString(); | ||
| return `${tokenDisplay} (${coloredPercentage})`; | ||
| }; | ||
|
|
||
| // Get context tokens from Claude Code hook data, or fall back to calculating from transcript | ||
| const contextDataResult = hookData.context_window != null | ||
| // Prefer context_window data from Claude Code hook if available | ||
| ? Result.succeed({ | ||
| inputTokens: hookData.context_window.total_input_tokens, | ||
| contextLimit: hookData.context_window.context_window_size, | ||
| }) | ||
| // Fall back to calculating context tokens from transcript | ||
| : await Result.try({ | ||
| try: async () => calculateContextTokens(hookData.transcript_path, hookData.model.id, mergedOptions.offline), | ||
| catch: error => error, | ||
| })(); | ||
|
|
||
| const contextInfo = Result.pipe( | ||
| contextDataResult, | ||
| Result.inspectError(error => logger.debug(`Failed to calculate context tokens: ${error instanceof Error ? error.message : String(error)}`)), | ||
| Result.map((contextResult) => { | ||
| if (contextResult == null) { | ||
| return undefined; | ||
| } | ||
| // Format context percentage with color coding using option thresholds | ||
| const color = contextResult.percentage < ctx.values.contextLowThreshold | ||
| ? pc.green | ||
| : contextResult.percentage < ctx.values.contextMediumThreshold | ||
| ? pc.yellow | ||
| : pc.red; | ||
| const coloredPercentage = color(`${contextResult.percentage}%`); | ||
|
|
||
| // Format token count with thousand separators | ||
| const tokenDisplay = contextResult.inputTokens.toLocaleString(); | ||
| return `${tokenDisplay} (${coloredPercentage})`; | ||
| return formatContextInfo(contextResult.inputTokens, contextResult.contextLimit); | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg "formatContextInfo" -lRepository: ryoppippi/ccusage
Length of output: 100
🏁 Script executed:
find / -name "statusline.ts" 2>/dev/nullRepository: ryoppippi/ccusage
Length of output: 119
🌐 Web query:
ccusage statusline.ts formatContextInfo Claude context tokens
💡 Result:
Do you want (A) an explanation of how formatContextInfo displays Claude context/token info, (B) the exact code for formatContextInfo in ccusage/ccstatusline, or (C) help debugging a statusline/ccusage token display bug? Which one?
🏁 Script executed:
sed -n '433,467p' /home/jailuser/git/apps/ccusage/src/commands/statusline.tsRepository: ryoppippi/ccusage
Length of output: 1711
🏁 Script executed:
rg "calculateContextTokens" /home/jailuser/git/apps/ccusage/src --type ts -A 15 | head -60Repository: ryoppippi/ccusage
Length of output: 5520
🏁 Script executed:
find /home/jailuser/git -name ".eslintrc*" -o -name "eslint.config.*" | head -5Repository: ryoppippi/ccusage
Length of output: 278
🏁 Script executed:
rg "formatContextInfo|calculateContextTokens" /home/jailuser/git/apps/ccusage --type ts -B 2 -A 5 | head -80Repository: ryoppippi/ccusage
Length of output: 6726
🏁 Script executed:
sed -n '/^export async function calculateContextTokens/,/^}/p' /home/jailuser/git/apps/ccusage/src/data-loader.ts | head -80Repository: ryoppippi/ccusage
Length of output: 253
🏁 Script executed:
cat /home/jailuser/git/apps/ccusage/eslint.config.js | grep -i "color\|colour" -A 2 -B 2Repository: ryoppippi/ccusage
Length of output: 43
🏁 Script executed:
grep -n "percentage" /home/jailuser/git/apps/ccusage/src/data-loader.ts | head -20Repository: ryoppippi/ccusage
Length of output: 447
🏁 Script executed:
sed -n '1280,1310p' /home/jailuser/git/apps/ccusage/src/data-loader.tsRepository: ryoppippi/ccusage
Length of output: 999
🏁 Script executed:
sed -n '440,480p' /home/jailuser/git/apps/ccusage/src/commands/statusline.tsRepository: ryoppippi/ccusage
Length of output: 1883
🏁 Script executed:
sed -n '433p' /home/jailuser/git/apps/ccusage/src/commands/statusline.tsRepository: ryoppippi/ccusage
Length of output: 125
🏁 Script executed:
git log --oneline -20 /home/jailuser/git/apps/ccusage/src/commands/statusline.ts 2>/dev/null | head -10Repository: ryoppippi/ccusage
Length of output: 109
🏁 Script executed:
cat /home/jailuser/git/eslint.config.js | head -100Repository: ryoppippi/ccusage
Length of output: 243
Add percentage bounds checking in formatContextInfo helper
The context data preference logic looks good. One refinement is recommended:
formatContextInfo should clamp percentage to [0, 100] to match the behavior of calculateContextTokens. Currently, calculateContextTokens clamps its returned percentage, but formatContextInfo recalculates the percentage without bounds, creating an inconsistency.
// Helper function to format context info with color coding
const formatContextInfo = (inputTokens: number, contextLimit: number): string => {
+ if (contextLimit <= 0) {
+ return 'N/A';
+ }
+
+ const rawPercentage = (inputTokens / contextLimit) * 100;
+ const percentage = Math.min(100, Math.max(0, Math.round(rawPercentage)));
- const percentage = Math.round((inputTokens / contextLimit) * 100);
const color = percentage < ctx.values.contextLowThreshold
? pc.green
: percentage < ctx.values.contextMediumThreshold
? pc.yellow
: pc.red;
const coloredPercentage = color(`${percentage}%`);
const tokenDisplay = inputTokens.toLocaleString();
return `${tokenDisplay} (${coloredPercentage})`;
};This ensures context usage coloring remains consistent with the existing calculateContextTokens clamping behavior.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Helper function to format context info with colour coding | |
| const formatContextInfo = (inputTokens: number, contextLimit: number): string => { | |
| const percentage = Math.round((inputTokens / contextLimit) * 100); | |
| const color = percentage < ctx.values.contextLowThreshold | |
| ? pc.green | |
| : percentage < ctx.values.contextMediumThreshold | |
| ? pc.yellow | |
| : pc.red; | |
| const coloredPercentage = color(`${percentage}%`); | |
| const tokenDisplay = inputTokens.toLocaleString(); | |
| return `${tokenDisplay} (${coloredPercentage})`; | |
| }; | |
| // Get context tokens from Claude Code hook data, or fall back to calculating from transcript | |
| const contextDataResult = hookData.context_window != null | |
| // Prefer context_window data from Claude Code hook if available | |
| ? Result.succeed({ | |
| inputTokens: hookData.context_window.total_input_tokens, | |
| contextLimit: hookData.context_window.context_window_size, | |
| }) | |
| // Fall back to calculating context tokens from transcript | |
| : await Result.try({ | |
| try: async () => calculateContextTokens(hookData.transcript_path, hookData.model.id, mergedOptions.offline), | |
| catch: error => error, | |
| })(); | |
| const contextInfo = Result.pipe( | |
| contextDataResult, | |
| Result.inspectError(error => logger.debug(`Failed to calculate context tokens: ${error instanceof Error ? error.message : String(error)}`)), | |
| Result.map((contextResult) => { | |
| if (contextResult == null) { | |
| return undefined; | |
| } | |
| // Format context percentage with color coding using option thresholds | |
| const color = contextResult.percentage < ctx.values.contextLowThreshold | |
| ? pc.green | |
| : contextResult.percentage < ctx.values.contextMediumThreshold | |
| ? pc.yellow | |
| : pc.red; | |
| const coloredPercentage = color(`${contextResult.percentage}%`); | |
| // Format token count with thousand separators | |
| const tokenDisplay = contextResult.inputTokens.toLocaleString(); | |
| return `${tokenDisplay} (${coloredPercentage})`; | |
| return formatContextInfo(contextResult.inputTokens, contextResult.contextLimit); | |
| }), | |
| // Helper function to format context info with color coding | |
| const formatContextInfo = (inputTokens: number, contextLimit: number): string => { | |
| if (contextLimit <= 0) { | |
| return 'N/A'; | |
| } | |
| const rawPercentage = (inputTokens / contextLimit) * 100; | |
| const percentage = Math.min(100, Math.max(0, Math.round(rawPercentage))); | |
| const color = percentage < ctx.values.contextLowThreshold | |
| ? pc.green | |
| : percentage < ctx.values.contextMediumThreshold | |
| ? pc.yellow | |
| : pc.red; | |
| const coloredPercentage = color(`${percentage}%`); | |
| const tokenDisplay = inputTokens.toLocaleString(); | |
| return `${tokenDisplay} (${coloredPercentage})`; | |
| }; | |
| // Get context tokens from Claude Code hook data, or fall back to calculating from transcript | |
| const contextDataResult = hookData.context_window != null | |
| // Prefer context_window data from Claude Code hook if available | |
| ? Result.succeed({ | |
| inputTokens: hookData.context_window.total_input_tokens, | |
| contextLimit: hookData.context_window.context_window_size, | |
| }) | |
| // Fall back to calculating context tokens from transcript | |
| : await Result.try({ | |
| try: async () => calculateContextTokens(hookData.transcript_path, hookData.model.id, mergedOptions.offline), | |
| catch: error => error, | |
| })(); | |
| const contextInfo = Result.pipe( | |
| contextDataResult, | |
| Result.inspectError(error => logger.debug(`Failed to calculate context tokens: ${error instanceof Error ? error.message : String(error)}`)), | |
| Result.map((contextResult) => { | |
| if (contextResult == null) { | |
| return undefined; | |
| } | |
| return formatContextInfo(contextResult.inputTokens, contextResult.contextLimit); | |
| }), |
🧰 Tools
🪛 GitHub Actions: CI
[error] 433-433: typos: 'colour' should be 'color'. Step 'nix develop --command typos --config ./typos.toml' failed with exit code 2.
🤖 Prompt for AI Agents
In apps/ccusage/src/commands/statusline.ts around lines 433 to 467,
formatContextInfo recalculates percentage without clamping, allowing values <0
or >100 and causing inconsistent coloring vs calculateContextTokens; change
formatContextInfo to clamp the computed percentage into the 0–100 range (e.g.
Math.max(0, Math.min(100, computedPercentage))) before choosing the color and
formatting the string so coloring and displayed percentage match
calculateContextTokens behavior.
|
Looks like the context_window value is not really usable: anthropics/claude-code#13783 |
Summary
Add support for the new
context_windowfield that Claude Code now provides in its statusline hook JSON data.What Changed
context_windowfield tostatuslineHookJsonSchemawithtotal_input_tokens,total_output_tokens, andcontext_window_sizecontext_windowdata from Claude Code when availablecontext_windowis not providedWhy
Claude Code now exposes context window information directly in its statusline hook, which is more accurate than parsing the transcript file. This change allows ccusage to display real-time context usage information from the source.
References
Summary by CodeRabbit
New Features
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.