feat: Add Claude usage limit reset time tracking and display#219
Conversation
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.
There was a problem hiding this comment.
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
usageDataSchemato capture relevant error message details and introducing a new utility functiongetUsageLimitResetTimeto 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 theusageLimitResetTime. This information is now propagated through the data processing pipeline and is also included in the JSON output of theblockscommand, 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
-
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. ↩
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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;There was a problem hiding this comment.
I decide that my implementation is more suitable for the implementation policy of the project.
| 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; |
There was a problem hiding this comment.
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)));There was a problem hiding this comment.
The null decision is judged appropriate because unnecessary processing can be skipped by making the null decision.
| costUSD: number | null; | ||
| model: string; | ||
| version?: string; | ||
| usageLimitResetTime?: Date; // Claude API usage limit reset time |
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
There was a problem hiding this comment.
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
📒 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.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
For async operations: create wrapper function withResult.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 useResult.isFailure()andResult.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 itexport constvs justconst
Model Naming Convention: Pattern isclaude-{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, usingif (import.meta.vitest != null)blocks
Vitest globals (describe,it,expect) are available automatically without imports
Dynamic imports usingawait import()should only be used within test blocks to avoid tree-shaking issues
Mock data is created usingfs-fixturewithcreateFixture()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.unwrapcalls 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
getUsageLimitResetTimeinto the session block data loading is well-implemented. The usage limit reset time is correctly extracted and included in theLoadedUsageEntryobject.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
getUsageLimitResetTimeand 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({...}))andz.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 issuesLearnt 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 dataLearnt 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 databaseLearnt 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 simulationLearnt 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 entriesLearnt 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 implementationLearnt 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
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 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 tsLength 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 tsLength 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 thetry { 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.

Summary
#194
Summary by CodeRabbit
New Features
Other