Skip to content

Conversation

@ryoppippi
Copy link
Owner

@ryoppippi ryoppippi commented Dec 11, 2025

SCR-20251211-slms

Summary

Add support for the new context_window field that Claude Code now provides in its statusline hook JSON data.

What Changed

  • Added context_window field to statuslineHookJsonSchema with total_input_tokens, total_output_tokens, and context_window_size
  • Updated statusline command to prefer context_window data from Claude Code when available
  • Falls back to existing transcript-based calculation when context_window is not provided
  • Refactored context info formatting into a reusable helper function using Result type pattern

Why

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

    • Status display now surfaces context-window metrics (input/output tokens, utilization %) with color-coded indicators and prefers reported context data when available.
  • Tests

    • Updated test fixtures to include context-window scenarios validating token metrics.
  • Documentation

    • Documentation updated to note that reported context-window data is used when available for more accurate statusline counts.

✏️ Tip: You can customize this high-level summary in your review settings.

…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.
@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This change adds an optional context_window object to the statusline hook schema, updates the statusline command to prefer hook-provided context data and centralizes context formatting via a new helper, updates test fixtures to include the new field, and documents the preference for Claude Code's context data.

Changes

Cohort / File(s) Summary
Type definitions
apps/ccusage/src/_types.ts
Added optional context_window to statuslineHookJsonSchema with total_input_tokens (number, required), total_output_tokens (number, optional), and context_window_size (number, required).
Command implementation
apps/ccusage/src/commands/statusline.ts
Added formatContextInfo helper; refactored context sourcing to prefer hookData.context_window and fall back to calculateContextTokens(); consolidated color/percentage/token formatting and adjusted error handling for token calculation.
Tests / Fixtures
apps/ccusage/test/statusline-test*.json
Added context_window objects to fixtures (statusline-test.json, statusline-test-opus4.json, statusline-test-sonnet4.json, statusline-test-sonnet41.json) containing total_input_tokens, total_output_tokens, and context_window_size.
Docs
docs/guide/statusline.md
Documented that Claude Code's context_window data is preferred when available for accurate token counts in the statusline.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Inspect statuslineHookJsonSchema usage sites to ensure the new optional field is handled safely.
  • Review formatContextInfo math and color-threshold logic for division-by-zero or off-by-one errors.
  • Verify fallback path and error logging around calculateContextTokens() in statusline.ts.
  • Confirm test fixtures and docs match schema additions.

Possibly related PRs

Poem

🐇
I hopped into hooks at break of day,
Counted tokens where snippets play,
Hooked a window wide and bright,
Colored numbers, neat and light,
A rabbit's cheer for context sight! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: adding support for the context_window field from Claude Code's statusline hook, which is the primary modification across the TypeScript types, implementation, and test files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36f376d and 555f414.

📒 Files selected for processing (2)
  • apps/ccusage/src/commands/statusline.ts (1 hunks)
  • docs/guide/statusline.md (1 hunks)

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 11, 2025

Open in StackBlitz

npm i https://pkg.pr.new/ryoppippi/ccusage@749
npm i https://pkg.pr.new/ryoppippi/ccusage/@ccusage/codex@749
npm i https://pkg.pr.new/ryoppippi/ccusage/@ccusage/mcp@749

commit: 555f414

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 11, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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.
@ryoppippi ryoppippi merged commit 7515103 into main Dec 11, 2025
15 checks passed
@ryoppippi ryoppippi deleted the feat/statusline-context-window branch December 11, 2025 21:23
Copy link

@coderabbitai coderabbitai bot left a 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 validation

The new context_window field 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0bd2914 and 36f376d.

📒 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 using if (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)
Use fs-fixture with createFixture() 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 from src/logger.ts instead

Files:

  • apps/ccusage/src/commands/statusline.ts
  • apps/ccusage/src/_types.ts
apps/ccusage/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/**/*.ts: NEVER use await import() dynamic imports anywhere (especially in tests)
Prefer @praha/byethrow Result type for error handling instead of try-catch
Use .ts extensions for local imports (e.g., import { foo } from './utils.ts')

Files:

  • apps/ccusage/src/commands/statusline.ts
  • apps/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.ts
  • apps/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.ts
  • apps/ccusage/src/_types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() for wrapping operations that may throw (JSON parsing, etc.)
Use Result.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 use Result.isFailure() and Result.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 using if (import.meta.vitest != null) blocks
CRITICAL: DO NOT use await import() dynamic imports anywhere in the codebase - this causes tree-shaking issues
CRITICAL: Never use dynamic imports with await 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 using fs-fixture with createFixture() 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.ts
  • apps/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, NOT claude-4-sonnet-20250514)

Files:

  • apps/ccusage/src/commands/statusline.ts
  • apps/ccusage/test/statusline-test-sonnet41.json
  • apps/ccusage/test/statusline-test.json
  • apps/ccusage/src/_types.ts
  • apps/ccusage/test/statusline-test-sonnet4.json
  • apps/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 schema

The new context_window block matches the StatuslineHookJson schema 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 schema

The context_window object shape and token/window values look correct and consistent with the updated statuslineHookJsonSchema.

apps/ccusage/test/statusline-test-sonnet41.json (1)

24-28: Sonnet 4.1 fixture now includes context_window; looks good

The added context_window section 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 expectations

The new context_window block has the correct shape and realistic token counts for a 200k context window; it should exercise the new preferred data path in statusline.

Comment on lines 433 to 467
// 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);
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg "formatContextInfo" -l

Repository: ryoppippi/ccusage

Length of output: 100


🏁 Script executed:

find / -name "statusline.ts" 2>/dev/null

Repository: 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.ts

Repository: ryoppippi/ccusage

Length of output: 1711


🏁 Script executed:

rg "calculateContextTokens" /home/jailuser/git/apps/ccusage/src --type ts -A 15 | head -60

Repository: ryoppippi/ccusage

Length of output: 5520


🏁 Script executed:

find /home/jailuser/git -name ".eslintrc*" -o -name "eslint.config.*" | head -5

Repository: ryoppippi/ccusage

Length of output: 278


🏁 Script executed:

rg "formatContextInfo|calculateContextTokens" /home/jailuser/git/apps/ccusage --type ts -B 2 -A 5 | head -80

Repository: 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 -80

Repository: ryoppippi/ccusage

Length of output: 253


🏁 Script executed:

cat /home/jailuser/git/apps/ccusage/eslint.config.js | grep -i "color\|colour" -A 2 -B 2

Repository: ryoppippi/ccusage

Length of output: 43


🏁 Script executed:

grep -n "percentage" /home/jailuser/git/apps/ccusage/src/data-loader.ts | head -20

Repository: ryoppippi/ccusage

Length of output: 447


🏁 Script executed:

sed -n '1280,1310p' /home/jailuser/git/apps/ccusage/src/data-loader.ts

Repository: ryoppippi/ccusage

Length of output: 999


🏁 Script executed:

sed -n '440,480p' /home/jailuser/git/apps/ccusage/src/commands/statusline.ts

Repository: ryoppippi/ccusage

Length of output: 1883


🏁 Script executed:

sed -n '433p' /home/jailuser/git/apps/ccusage/src/commands/statusline.ts

Repository: 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 -10

Repository: ryoppippi/ccusage

Length of output: 109


🏁 Script executed:

cat /home/jailuser/git/eslint.config.js | head -100

Repository: 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.

Suggested change
// 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.

@shailendher
Copy link

Looks like the context_window value is not really usable: anthropics/claude-code#13783

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants