feat(usage): read rate_limits from stdin instead of calling Anthropic API#105
feat(usage): read rate_limits from stdin instead of calling Anthropic API#105yearth wants to merge 3 commits into
Conversation
Reviewer's GuideUsageSegment now preferentially consumes rate limit data passed via stdin ( Sequence diagram for UsageSegment collect with stdin and API fallbacksequenceDiagram
actor User
participant ClaudeCode as ClaudeCode_process
participant Stdin as Stdin_Input
participant Client as UsageSegment
participant Config as Config_loader
participant Cache as FileCache
participant Api as Anthropic_API
User->>ClaudeCode: Request statusline render
ClaudeCode->>Stdin: Write InputData (including rate_limits)
ClaudeCode->>Client: collect(input)
activate Client
Client->>Client: collect_from_stdin(input)
alt rate_limits present and valid
Client->>Client: parse_utilization(five_hour)
Client->>Client: parse_utilization(seven_day)
Client->>Client: format_reset_time_unix(resets_at)
Client->>Client: build_segment_data()
Client-->>ClaudeCode: SegmentData
else no usable stdin rate_limits
Client->>Client: collect_from_api()
Client->>Config: load()
Config-->>Client: segment_config
Client->>Cache: load_cache()
Cache-->>Client: ApiUsageCache?
Client->>Client: is_cache_valid(cache, cache_duration)
alt cache valid
Client->>Client: build_segment_data(from cache)
Client-->>ClaudeCode: SegmentData
else cache missing or invalid
Client->>Api: fetch_api_usage(token)
alt API success
Api-->>Client: ApiUsageResponse
Client->>Client: format_reset_time_rfc3339(resets_at)
Client->>Cache: save_cache(new ApiUsageCache)
Client->>Client: build_segment_data(from API)
Client-->>ClaudeCode: SegmentData
else API failure
alt cached_data exists
Client->>Client: build_segment_data(from stale cache)
Client-->>ClaudeCode: SegmentData
else no cache
Client-->>ClaudeCode: None
end
end
end
end
deactivate Client
Class diagram for updated usage rate limit types and UsageSegmentclassDiagram
direction LR
class InputData {
+Model model
+Workspace workspace
+String transcript_path
+Cost cost
+OutputStyle output_style
+RateLimits rate_limits
}
class RateLimits {
+RateLimitPeriod five_hour
+RateLimitPeriod seven_day
}
class RateLimitPeriod {
+f64 used_percentage
+i64 resets_at
}
class Segment {
<<interface>> Segment
+collect(input InputData) SegmentData
+id() SegmentId
}
class UsageSegment {
+new() UsageSegment
+collect(input InputData) SegmentData
-collect_from_stdin(input InputData) SegmentData
-collect_from_api() SegmentData
-build_segment_data(five_hour_util f64, seven_day_util f64, reset_str String) SegmentData
-parse_utilization(period RateLimitPeriod) f64
-get_circle_icon(utilization f64) String
-format_reset_time_unix(ts i64) String
-format_reset_time_rfc3339(reset_time_str String) String
-get_cache_path() PathBuf
-load_cache() ApiUsageCache
-save_cache(cache ApiUsageCache)
-is_cache_valid(cache ApiUsageCache, cache_duration u64) bool
-get_claude_code_version() String
-get_proxy_from_settings() String
-fetch_api_usage(api_base_url String, token String, timeout u64) ApiUsageResponse
}
class SegmentData {
+String primary
+String secondary
+HashMap~String, String~ metadata
}
class ApiUsageResponse {
+ApiUsagePeriod five_hour
+ApiUsagePeriod seven_day
}
class ApiUsagePeriod {
+f64 utilization
+String resets_at
}
class ApiUsageCache {
+f64 five_hour_utilization
+f64 seven_day_utilization
+String resets_at
+String cached_at
}
Segment <|.. UsageSegment
InputData --> RateLimits : has
RateLimits --> RateLimitPeriod : has
UsageSegment --> SegmentData : builds
UsageSegment --> ApiUsageResponse : uses
UsageSegment --> ApiUsageCache : caches
UsageSegment --> InputData : reads
ApiUsageResponse --> ApiUsagePeriod : contains
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
fetch_api_usagethe newlet _ = credentials::get_oauth_token();call is redundant since the OAuth token is already passed in as a parameter and the result is discarded; consider removing this to avoid confusion and an extra credentials lookup. collect_from_apitakes aninput: &InputDataparameter that is never used; you can drop this argument (and its uses at the call site) to keep the API cleaner and avoid unused-parameter warnings.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `fetch_api_usage` the new `let _ = credentials::get_oauth_token();` call is redundant since the OAuth token is already passed in as a parameter and the result is discarded; consider removing this to avoid confusion and an extra credentials lookup.
- `collect_from_api` takes an `input: &InputData` parameter that is never used; you can drop this argument (and its uses at the call site) to keep the API cleaner and avoid unused-parameter warnings.
## Individual Comments
### Comment 1
<location path="src/core/segments/usage.rs" line_range="181" />
<code_context>
+ fn fetch_api_usage(
</code_context>
<issue_to_address>
**suggestion:** Extra `get_oauth_token` call in `fetch_api_usage` appears redundant and adds an unnecessary dependency on `credentials` here.
Inside `fetch_api_usage`, the new `let _ = credentials::get_oauth_token();` doesn’t change behavior: the passed-in `token` is what’s used for the `Authorization` header. This call just adds an extra dependency and potential failure/log path unrelated to the actual token in use. Unless you need a specific side effect from `get_oauth_token`, consider removing it so `fetch_api_usage` depends only on its `token` argument.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
collect_from_stdin, treating missingseven_day.used_percentageas0.0and then requiringseven_day_util > 0.0to render whenfive_houris absent can suppress the segment (and force an API fallback) even when valid 0% usage is reported; consider keepingOption<f64>for seven-day utilization through the selection logic so thatSome(0.0)is distinguishable fromNone. - Similarly, defaulting a missing
seven_dayutilization to0.0before passing it intobuild_segment_datameans consumers ofmetadata["seven_day_utilization"]cannot tell the difference between a truly missing value and a real 0% usage; if that distinction matters, consider storing the rawOption(e.g., via a separate presence flag) instead of conflating them.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `collect_from_stdin`, treating missing `seven_day.used_percentage` as `0.0` and then requiring `seven_day_util > 0.0` to render when `five_hour` is absent can suppress the segment (and force an API fallback) even when valid 0% usage is reported; consider keeping `Option<f64>` for seven-day utilization through the selection logic so that `Some(0.0)` is distinguishable from `None`.
- Similarly, defaulting a missing `seven_day` utilization to `0.0` before passing it into `build_segment_data` means consumers of `metadata["seven_day_utilization"]` cannot tell the difference between a truly missing value and a real 0% usage; if that distinction matters, consider storing the raw `Option` (e.g., via a separate presence flag) instead of conflating them.
## Individual Comments
### Comment 1
<location path="src/core/segments/usage.rs" line_range="238" />
<code_context>
+ .unwrap_or(0.0);
+
+ // Require at least one valid utilization value to show the segment
+ let display_util = five_hour_util.or(if seven_day_util > 0.0 { Some(seven_day_util) } else { None })?;
+
+ let reset_str = rate_limits
</code_context>
<issue_to_address>
**issue:** The fallback to seven_day utilization skips valid 0% values when five_hour is missing.
In the case where `five_hour_util` is `None` and `seven_day_util` is `Some(0.0)`, this returns `None` and hides the segment, making `0%` usage indistinguishable from “no data”. If `0%` is a valid state to display, consider using the presence of the `Option` (e.g. `if let Some(v) = seven_day_util_opt { ... }`) rather than `> 0.0`, or track seven-day availability separately from its numeric value.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| .unwrap_or(0.0); | ||
|
|
||
| // Require at least one valid utilization value to show the segment | ||
| let display_util = five_hour_util.or(if seven_day_util > 0.0 { Some(seven_day_util) } else { None })?; |
There was a problem hiding this comment.
issue: The fallback to seven_day utilization skips valid 0% values when five_hour is missing.
In the case where five_hour_util is None and seven_day_util is Some(0.0), this returns None and hides the segment, making 0% usage indistinguishable from “no data”. If 0% is a valid state to display, consider using the presence of the Option (e.g. if let Some(v) = seven_day_util_opt { ... }) rather than > 0.0, or track seven-day availability separately from its numeric value.
Background
Claude Code passes rate limit data directly through stdin on every render:
{ "rate_limits": { "five_hour": { "used_percentage": 75.0, "resets_at": 1711270800 }, "seven_day": { "used_percentage": 45.0, "resets_at": 1711876200 } } }The current
UsageSegmentignores this and instead makes an outbound HTTP request toapi.anthropic.com/api/oauth/usageon every render (with a file-based cache to reduce frequency). This works, but it has real costs.Problems with the current approach
Latency on cache miss: When the cache expires, the segment blocks on a network request before the statusline can render. On slow connections or when the API is temporarily unreachable, this causes a noticeable delay or a missing segment.
Credential dependency: The segment reads an OAuth token from
~/.claude/.credentials.json. Users who authenticate via SSO, enterprise proxies, or non-standard setups may not have this token available, causing the segment to silently disappear.Stale data: The cache has a fixed TTL (default 300s). The API response reflects usage at the time of the last request, not the current moment. The stdin value is computed by Claude Code itself and is always current.
Unnecessary complexity: The implementation includes proxy detection, npm version querying for the User-Agent header, cache file management, and OAuth token parsing — none of which are needed if the data is already in stdin.
This PR
rate_limitsfield toInputData(parsed from stdin, fully optional for backward compatibility)UsageSegment::collectnow tries stdin first viacollect_from_stdinrate_limitsis absent (older Claude Code versions), it falls back tocollect_from_api— the existing behavior is fully preservedget_circle_iconnow returns&'static strto avoid per-render allocationsResult
For users on a current Claude Code version: zero network calls, zero credential reads, always-fresh data, instant render.
For users on older versions: identical behavior to before.
Tests
5 unit tests covering stdin parsing, clamping, missing data, non-finite values, and icon boundaries.
Summary by Sourcery
Read Claude Code rate limit usage from stdin when available and fall back to the existing Anthropic usage API, keeping the Usage segment behavior compatible with older clients.
New Features:
Enhancements:
Tests: