feat: Add Claude usage limit reset time tracking and display by salala01 · Pull Request #219 · ccusage/ccusage · GitHub
Skip to content

feat: Add Claude usage limit reset time tracking and display#219

Merged
ryoppippi merged 7 commits into
ccusage:mainfrom
salala01:feat/usage-limit
Jul 4, 2025
Merged

feat: Add Claude usage limit reset time tracking and display#219
ryoppippi merged 7 commits into
ccusage:mainfrom
salala01:feat/usage-limit

Conversation

@salala01

@salala01 salala01 commented Jun 29, 2025

Copy link
Copy Markdown
Contributor

Summary

  • Add Claude API usage limit reset time tracking and display
  • Show usage limit warnings in live monitoring with reset time
  • Include usage limit information in JSON output for blocks command
  • Only display usage limit message when reset time is in the future

#194

image

Summary by CodeRabbit

  • New Features

    • Displays a warning message with reset time when Claude API usage limits are reached in the live display.
    • Includes usage limit reset time in session block details and JSON output.
  • Other

    • Enhanced session and usage data to optionally include usage limit reset time information.

salala01 added 5 commits June 29, 2025 14:56
Track Claude API usage limit reset timestamps from error messages and propagate through data structures for quota monitoring.
Add visual indicator for Claude usage limit with reset time display in live monitoring interface. Shows red warning message with countdown when usage limit is active.
Prevents displaying usage limit message after the reset time has passed
by checking if current time is before the reset time.
@coderabbitai

coderabbitai Bot commented Jun 29, 2025

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary of Changes

Hello @salala01, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces comprehensive support for tracking and displaying Claude API usage limit reset times. It enhances the application's ability to detect usage limit errors, extract the specific reset timestamp from API responses, and then present this crucial information to the user both in the live monitoring interface and through the blocks command's JSON output. This helps users understand when they can resume API calls after hitting a limit.

Highlights

  • Tracking Claude API Usage Limits: Implemented logic to parse specific Claude API error messages to extract and track the usage limit reset time. This involves enhancing the usageDataSchema to capture relevant error message details and introducing a new utility function getUsageLimitResetTime to extract the timestamp.
  • Live Monitoring Display: Added a visual warning to the live monitoring display. When a Claude API usage limit is encountered and a future reset time is available, a prominent red message '❌ USAGE LIMIT. RESET AT ' is now displayed, providing immediate feedback to the user.
  • Data Model and Output Enhancement: Extended the internal data models (LoadedUsageEntry, SessionBlock) to include the usageLimitResetTime. This information is now propagated through the data processing pipeline and is also included in the JSON output of the blocks command, allowing programmatic access to usage limit details.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a feature for tracking and displaying Claude API usage limit reset times. The implementation is well-thought-out. My feedback focuses on improving the code's robustness and readability. I've suggested a refactor to make parsing the reset time from an API error message clearer and easier to maintain, and proposed simplifying a section of the rendering logic. Addressing these points will make the new functionality more robust.

Comment thread src/data-loader.ts
Comment on lines +608 to +611
if (data.isApiErrorMessage === true) {
const timestampMatch = data.message?.content?.find(
c => c.text != null && c.text.includes('Claude AI usage limit reached'),
)?.text?.match(/\|(\d+)/) ?? null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Parsing the reset time from a hardcoded error message string is fragile. If the API error message format changes, this logic will break. Extract the magic string 'Claude AI usage limit reached' and the timestamp-matching regular expression into named constants to improve maintainability. Simplify the find condition using optional chaining.

const USAGE_LIMIT_MESSAGE_TEXT = 'Claude AI usage limit reached';
		const TIMESTAMP_REGEX = /\|(\d+)/;
		const timestampMatch = data.message?.content
			?.find(c => c.text?.includes(USAGE_LIMIT_MESSAGE_TEXT))
			?.text?.match(TIMESTAMP_REGEX) ?? null;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I decide that my implementation is more suitable for the implementation policy of the project.

Comment thread src/_live-rendering.ts
Comment on lines +164 to +167
if (block.usageLimitResetTime !== undefined && now < block.usageLimitResetTime) {
const resetTime = block.usageLimitResetTime?.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: true }) ?? null;
const usageLimitResetTime = resetTime !== null ? pc.red(`❌ USAGE LIMIT. RESET AT ${resetTime}`) : '';
usageLimitResetTimePadded = resetTime !== null ? usageLimitResetTime + ' '.repeat(Math.max(0, boxWidth - 3 - stringWidth(usageLimitResetTime))) : null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This block can be simplified. Since the if condition already ensures block.usageLimitResetTime is a Date object, the optional chaining (?.) and null-coalescing (?? null) on line 165 are redundant. Similarly, the repeated resetTime !== null checks are unnecessary. Renaming usageLimitResetTime to usageLimitMessage would more accurately describe its content.

const resetTime = block.usageLimitResetTime.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: true });
		const usageLimitMessage = pc.red(`❌ USAGE LIMIT. RESET AT ${resetTime}`);
		usageLimitResetTimePadded = usageLimitMessage + ' '.repeat(Math.max(0, boxWidth - 3 - stringWidth(usageLimitMessage)));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The null decision is judged appropriate because unnecessary processing can be skipped by making the null decision.

Comment thread src/_session-blocks.ts
costUSD: number | null;
model: string;
version?: string;
usageLimitResetTime?: Date; // Claude API usage limit reset time

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Consider adding a more descriptive comment to clarify the purpose of usageLimitResetTime.

ryoppippi added 2 commits July 4, 2025 20:09
Merges usage limit tracking functionality including:

- Add usage limit reset time tracking

- Display usage limit reset time in live rendering

- Add usageLimitResetTime to JSON output

- Fix usage limit message timing to only show when reset time is in future

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/data-loader.ts (2)

601-622: Consider extracting magic strings and improve robustness.

The function correctly extracts usage limit reset times from API error messages, but has some areas for improvement:

+const USAGE_LIMIT_MESSAGE_TEXT = 'Claude AI usage limit reached';
+const TIMESTAMP_REGEX = /\|(\d+)/;

 export function getUsageLimitResetTime(data: UsageData): Date | null {
 	let resetTime: Date | null = null;
 
 	if (data.isApiErrorMessage === true) {
-		const timestampMatch = data.message?.content?.find(
-			c => c.text != null && c.text.includes('Claude AI usage limit reached'),
-		)?.text?.match(/\|(\d+)/) ?? null;
+		const timestampMatch = data.message?.content
+			?.find(c => c.text?.includes(USAGE_LIMIT_MESSAGE_TEXT))
+			?.text?.match(TIMESTAMP_REGEX)?.[1] ?? null;
 
-		if (timestampMatch?.[1] != null) {
-			const resetTimestamp = Number.parseInt(timestampMatch[1]);
+		if (timestampMatch != null) {
+			const resetTimestamp = Number.parseInt(timestampMatch);
 			resetTime = resetTimestamp > 0 ? new Date(resetTimestamp * 1000) : null;
 		}
 	}

851-887: Same error handling pattern change as previous comment.

This follows the same pattern change from Result-based to try-catch error handling. The consistency concern applies here as well.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86d326b and 43b573f.

📒 Files selected for processing (3)
  • src/_session-blocks.ts (4 hunks)
  • src/commands/blocks.ts (1 hunks)
  • src/data-loader.ts (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/commands/blocks.ts
  • src/_session-blocks.ts
🧰 Additional context used
📓 Path-based instructions (3)
`**/*.{js,jsx,ts,tsx}`: Lint code using ESLint MCP server (available via Claude ...

**/*.{js,jsx,ts,tsx}: Lint code using ESLint MCP server (available via Claude Code tools)
Format code with ESLint (writes changes)
Uses ESLint for linting and formatting with tab indentation and double quotes
No console.log allowed except where explicitly disabled with eslint-disable
Do not use console.log. Use logger.ts instead

📄 Source: CodeRabbit Inference Engine (CLAUDE.md)

List of files the instruction was applied to:

  • src/data-loader.ts
`**/*.{ts,tsx}`: Type check with TypeScript Build distribution files with tsdown...

**/*.{ts,tsx}: Type check with TypeScript
Build distribution files with tsdown
TypeScript with strict mode and bundler module resolution
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
For async operations: create wrapper function with Result.try() then call it
Keep traditional try-catch only for: file I/O with complex error handling, legacy code that's hard to refactor
Always use Result.isFailure() and Result.isSuccess() type guards for better code clarity
Variables: start with lowercase (camelCase) - e.g., usageDataSchema, modelBreakdownSchema
Types: start with uppercase (PascalCase) - e.g., UsageData, ModelBreakdown
Constants: can use UPPER_SNAKE_CASE - e.g., DEFAULT_CLAUDE_CODE_PATH
Only export constants, functions, and types that are actually used by other modules
Internal/private constants that are only used within the same file should NOT be exported
Always check if a constant is used elsewhere before making it export const vs just const
Model Naming Convention: Pattern is claude-{model-type}-{generation}-{date} (e.g., claude-sonnet-4-20250514), generation number comes AFTER the model type
Tests are written directly in the same files as the source code, not in separate test files, using if (import.meta.vitest != null) blocks
Vitest globals (describe, it, expect) are available automatically without imports
Dynamic imports using await import() should only be used within test blocks to avoid tree-shaking issues
Mock data is created using fs-fixture with createFixture() for Claude data directory simulation
All test files must use current Claude 4 models, not outdated Claude 3 models
Test coverage should include both Sonnet and Opus models for comprehensive validation
Model names in tests must exactly match LiteLLM's pricing database entries
When adding new model tests, verify the model exists in LiteLLM before implementation
Tests depend on real pricing data from LiteLLM - failures may indicate model availability issues
Cost calculations require exact model name matches with LiteLLM's database
Test failures often indicate model names don't exist in LiteLLM's pricing data
The application cannot calculate costs for models not supported by LiteLLM

📄 Source: CodeRabbit Inference Engine (CLAUDE.md)

List of files the instruction was applied to:

  • src/data-loader.ts
`**/*.ts`: File paths always use Node.js path utilities for cross-platform compatibility

**/*.ts: File paths always use Node.js path utilities for cross-platform compatibility

📄 Source: CodeRabbit Inference Engine (CLAUDE.md)

List of files the instruction was applied to:

  • src/data-loader.ts
🧠 Learnings (1)
src/data-loader.ts (5)

undefined

<retrieved_learning>
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Tests depend on real pricing data from LiteLLM - failures may indicate model availability issues
</retrieved_learning>

<retrieved_learning>
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Variables: start with lowercase (camelCase) - e.g., usageDataSchema, modelBreakdownSchema
</retrieved_learning>

<retrieved_learning>
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Cost calculations require exact model name matches with LiteLLM's database
</retrieved_learning>

<retrieved_learning>
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Types: start with uppercase (PascalCase) - e.g., UsageData, ModelBreakdown
</retrieved_learning>

<retrieved_learning>
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Constants: can use UPPER_SNAKE_CASE - e.g., DEFAULT_CLAUDE_CODE_PATH
</retrieved_learning>

🧬 Code Graph Analysis (1)
src/data-loader.ts (2)
src/_types.ts (1)
  • requestIdSchema (17-19)
src/logger.ts (1)
  • logger (18-18)
🪛 ESLint
src/data-loader.ts

[error] 137-139: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 137-139: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 137-137: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 137-137: Unsafe member access .array on an error typed value.

(ts/no-unsafe-member-access)


[error] 137-137: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 137-137: Unsafe member access .object on an error typed value.

(ts/no-unsafe-member-access)


[error] 138-138: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 138-138: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 138-138: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 138-138: Unsafe member access .string on an error typed value.

(ts/no-unsafe-member-access)


[error] 138-138: Unsafe member access .optional on an error typed value.

(ts/no-unsafe-member-access)


[error] 139-139: Unsafe member access .optional on an error typed value.

(ts/no-unsafe-member-access)


[error] 141-141: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 141-141: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 141-141: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 141-141: Unsafe member access .number on an error typed value.

(ts/no-unsafe-member-access)


[error] 141-141: Unsafe member access .optional on an error typed value.

(ts/no-unsafe-member-access)


[error] 142-142: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 142-142: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 142-142: Unsafe member access .optional on an error typed value.

(ts/no-unsafe-member-access)


[error] 143-143: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 143-143: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 143-143: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 143-143: Unsafe member access .boolean on an error typed value.

(ts/no-unsafe-member-access)


[error] 143-143: Unsafe member access .optional on an error typed value.

(ts/no-unsafe-member-access)


[error] 580-580: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 580-580: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 580-580: Unsafe member access .unwrap on an error typed value.

(ts/no-unsafe-member-access)


[error] 580-580: Unsafe argument of type error typed assigned to a parameter of type { input_tokens: number; output_tokens: number; cache_creation_input_tokens?: number | undefined; cache_read_input_tokens?: number | undefined; }.

(ts/no-unsafe-argument)


[error] 580-580: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 580-580: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 580-580: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 592-592: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 592-592: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 592-592: Unsafe member access .unwrap on an error typed value.

(ts/no-unsafe-member-access)


[error] 592-592: Unsafe argument of type error typed assigned to a parameter of type { input_tokens: number; output_tokens: number; cache_creation_input_tokens?: number | undefined; cache_read_input_tokens?: number | undefined; }.

(ts/no-unsafe-argument)


[error] 592-592: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 592-592: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 592-592: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 609-609: Unsafe member access .isApiErrorMessage on an error typed value.

(ts/no-unsafe-member-access)


[error] 610-612: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 610-612: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 610-610: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 610-610: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 611-611: Unsafe return of a value of type any.

(ts/no-unsafe-return)


[error] 611-611: Unsafe member access .text on an any value.

(ts/no-unsafe-member-access)


[error] 611-611: Unsafe call of a(n) any typed value.

(ts/no-unsafe-call)


[error] 611-611: Unsafe member access .text on an any value.

(ts/no-unsafe-member-access)


[error] 612-612: Unsafe member access .text on an error typed value.

(ts/no-unsafe-member-access)


[error] 614-614: Unsafe member access [1] on an error typed value.

(ts/no-unsafe-member-access)


[error] 615-615: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 615-615: Unsafe member access [1] on an error typed value.

(ts/no-unsafe-member-access)


[error] 693-693: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 694-694: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 694-694: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 694-694: Unsafe member access .safeParse on an error typed value.

(ts/no-unsafe-member-access)


[error] 695-695: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 695-695: Unsafe member access .success on an error typed value.

(ts/no-unsafe-member-access)


[error] 698-698: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 698-698: Unsafe member access .data on an error typed value.

(ts/no-unsafe-member-access)


[error] 710-710: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 710-710: Unsafe member access .timestamp on an error typed value.

(ts/no-unsafe-member-access)


[error] 713-715: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 715-715: Unsafe member access .costUSD on an error typed value.

(ts/no-unsafe-member-access)


[error] 717-717: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 717-717: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 717-717: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 717-717: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 852-852: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 853-853: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 853-853: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 853-853: Unsafe member access .safeParse on an error typed value.

(ts/no-unsafe-member-access)


[error] 854-854: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 854-854: Unsafe member access .success on an error typed value.

(ts/no-unsafe-member-access)


[error] 857-857: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 857-857: Unsafe member access .data on an error typed value.

(ts/no-unsafe-member-access)


[error] 870-872: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 872-872: Unsafe member access .costUSD on an error typed value.

(ts/no-unsafe-member-access)


[error] 875-875: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 877-877: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 878-878: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 879-879: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 880-880: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 880-880: Unsafe member access .timestamp on an error typed value.

(ts/no-unsafe-member-access)


[error] 881-881: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 881-881: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 1077-1077: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 1078-1078: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1078-1078: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 1078-1078: Unsafe member access .safeParse on an error typed value.

(ts/no-unsafe-member-access)


[error] 1079-1079: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 1079-1079: Unsafe member access .success on an error typed value.

(ts/no-unsafe-member-access)


[error] 1082-1082: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1082-1082: Unsafe member access .data on an error typed value.

(ts/no-unsafe-member-access)


[error] 1094-1096: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1096-1096: Unsafe member access .costUSD on an error typed value.

(ts/no-unsafe-member-access)


[error] 1102-1102: Unsafe argument of type error typed assigned to a parameter of type string | number | Date.

(ts/no-unsafe-argument)


[error] 1102-1102: Unsafe member access .timestamp on an error typed value.

(ts/no-unsafe-member-access)


[error] 1104-1104: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1104-1104: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 1105-1105: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1105-1105: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 1106-1106: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1106-1106: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 1107-1107: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1107-1107: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 1109-1109: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1110-1110: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1110-1110: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 1111-1111: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 1111-1111: Unsafe member access .version on an error typed value.

(ts/no-unsafe-member-access)


[error] 1117-1117: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 1117-1117: Unsafe member access .debug on an error typed value.

(ts/no-unsafe-member-access)

🪛 Biome (1.9.4)
src/data-loader.ts

[error] 611-611: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (4)
src/data-loader.ts (4)

580-580: LGTM! Improved error handling with fallback values.

The addition of fallback values (0) to Result.unwrap calls is a good defensive programming practice that prevents the function from throwing when cost calculation fails.

Also applies to: 592-592


1076-1119: Good integration of usage limit reset time functionality.

The integration of getUsageLimitResetTime into the session block data loading is well-implemented. The usage limit reset time is correctly extracted and included in the LoadedUsageEntry object.

The error handling change from Result-based to try-catch follows the same pattern as other functions in this PR, maintaining consistency within the changes while the broader consistency question is addressed separately.


1098-1099: Clear and descriptive comment.

The comment clearly explains the purpose of calling getUsageLimitResetTime and helps with code readability.


137-143: Fix unsafe type operations in schema definition.

The static analysis correctly identifies unsafe type operations in the Zod schema definition. The z.array(z.object({...})) and z.boolean() calls are being flagged as unsafe due to improper typing.

The schema extension appears functionally correct for adding content array and API error message flag support, but needs proper typing to resolve the unsafe operations.

	message: z.object({
		usage: z.object({
			input_tokens: z.number(),
			output_tokens: z.number(),
			cache_creation_input_tokens: z.number().optional(),
			cache_read_input_tokens: z.number().optional(),
		}),
		model: modelNameSchema.optional(), // Model is inside message object
		id: messageIdSchema.optional(), // Message ID for deduplication
-		content: z.array(z.object({
-			text: z.string().optional(),
-		})).optional(),
+		content: z.array(z.object({
+			text: z.string().optional(),
+		})).optional(),
	}),
	costUSD: z.number().optional(), // Made optional for new schema
	requestId: requestIdSchema.optional(), // Request ID for deduplication
-	isApiErrorMessage: z.boolean().optional(),
+	isApiErrorMessage: z.boolean().optional(),
⛔ Skipped due to learnings
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Variables: start with lowercase (camelCase) - e.g., `usageDataSchema`, `modelBreakdownSchema`
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Tests depend on real pricing data from LiteLLM - failures may indicate model availability issues
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Test failures often indicate model names don't exist in LiteLLM's pricing data
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Types: start with uppercase (PascalCase) - e.g., `UsageData`, `ModelBreakdown`
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Cost calculations require exact model name matches with LiteLLM's database
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Mock data is created using `fs-fixture` with `createFixture()` for Claude data directory simulation
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : Model names in tests must exactly match LiteLLM's pricing database entries
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : When adding new model tests, verify the model exists in LiteLLM before implementation
Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-02T14:48:21.506Z
Learning: Applies to **/*.{ts,tsx} : All test files must use current Claude 4 models, not outdated Claude 3 models

Comment thread src/data-loader.ts
Comment on lines +692 to 722
try {
const parsed = JSON.parse(line) as unknown;
const result = usageDataSchema.safeParse(parsed);
if (!result.success) {
continue;
}
const data = result.data;

// Check for duplicate message + request ID combination
const uniqueHash = createUniqueHash(data);
if (isDuplicateEntry(uniqueHash, processedHashes)) {
// Skip duplicate message
continue;
}
// Check for duplicate message + request ID combination
const uniqueHash = createUniqueHash(data);
if (isDuplicateEntry(uniqueHash, processedHashes)) {
// Skip duplicate message
continue;
}

// Mark this combination as processed
markAsProcessed(uniqueHash, processedHashes);
// Mark this combination as processed
markAsProcessed(uniqueHash, processedHashes);

const date = formatDate(data.timestamp);
// If fetcher is available, calculate cost based on mode and tokens
// If fetcher is null, use pre-calculated costUSD or default to 0
const cost = fetcher != null
? await calculateCostForEntry(data, mode, fetcher)
: data.costUSD ?? 0;
const date = formatDate(data.timestamp);
// If fetcher is available, calculate cost based on mode and tokens
// If fetcher is null, use pre-calculated costUSD or default to 0
const cost = fetcher != null
? await calculateCostForEntry(data, mode, fetcher)
: data.costUSD ?? 0;

allEntries.push({ data, date, cost, model: data.message.model });
allEntries.push({ data, date, cost, model: data.message.model });
}
catch {
// Skip invalid JSON lines
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify error handling consistency across codebase.

The change from Result.try() to direct try-catch blocks deviates from the coding guidelines which prefer Result-based error handling. This inconsistency should be addressed.

The coding guidelines state: "Prefer @praha/byethrow Result type over traditional try-catch for functional error handling" and "Keep traditional try-catch only for: file I/O with complex error handling, legacy code that's hard to refactor."

Please verify if this change aligns with the project's error handling strategy:


🏁 Script executed:

#!/bin/bash
# Check for other uses of Result.try with JSON parsing in the codebase
rg "Result\.try.*JSON\.parse" --type ts

Length of output: 41


🏁 Script executed:

#!/bin/bash
# List all uses of Result.try in TypeScript files
rg "Result\\.try" --type ts

# List all JSON.parse occurrences to inspect their error handling
rg "JSON\\.parse" -n --type ts

Length of output: 1967


Refactor JSON parsing to use Result.try for consistent error handling

Per our coding guidelines, any operation that may throw (e.g. JSON.parse) should be wrapped in Result.try() and handled via Result.isFailure()/Result.isSuccess() rather than a raw try-catch. Traditional try-catch is reserved for complex file I/O or legacy code that can’t be refactored.

Please update the following spots:

  • src/data-loader.ts (around lines 693–722):
    Replace the try { JSON.parse… } catch { … } block with something along the lines of:
    const parseResult = Result.try(() => JSON.parse(line) as unknown)
      .andThen(parsed => usageDataSchema.safeParse(parsed));
    if (Result.isFailure(parseResult)) {
      continue;
    }
    const data = parseResult.value.data;
    // …rest of logic…
  • Other JSON.parse usages to migrate:
    • src/data-loader.ts: line 504, 852, 1077
    • src/_live-monitor.ts: line 108
    • src/mcp.ts: lines 504, 552, 594, 636, 715, 785, 921, 955, 986, 1033–1036

Making these changes will ensure uniform, functional error handling across the codebase.

🧰 Tools
🪛 ESLint

[error] 693-693: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 694-694: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 694-694: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 694-694: Unsafe member access .safeParse on an error typed value.

(ts/no-unsafe-member-access)


[error] 695-695: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 695-695: Unsafe member access .success on an error typed value.

(ts/no-unsafe-member-access)


[error] 698-698: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 698-698: Unsafe member access .data on an error typed value.

(ts/no-unsafe-member-access)


[error] 710-710: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 710-710: Unsafe member access .timestamp on an error typed value.

(ts/no-unsafe-member-access)


[error] 713-715: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 715-715: Unsafe member access .costUSD on an error typed value.

(ts/no-unsafe-member-access)


[error] 717-717: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 717-717: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 717-717: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 717-717: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)

🤖 Prompt for AI Agents
In src/data-loader.ts around lines 693 to 722, replace the try-catch block used
for JSON.parse with a functional error handling approach using Result.try().
Wrap the JSON.parse call inside Result.try(), then chain it with
usageDataSchema.safeParse using andThen. Check if the result is a failure with
Result.isFailure(), and if so, continue to the next iteration. If successful,
extract the parsed data from parseResult.value.data and proceed with the
existing logic. Apply similar refactoring to other JSON.parse usages in the
specified lines and files to maintain consistent error handling.

@ryoppippi ryoppippi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@pkg-pr-new

pkg-pr-new Bot commented Jul 4, 2025

Copy link
Copy Markdown

@ryoppippi ryoppippi merged commit b7171a9 into ccusage:main Jul 4, 2025
7 checks passed
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.

2 participants