Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

## [Windows] Unreleased

- **Claude**: the OAuth account email no longer delays a usage refresh. The profile lookup runs in the background, once per access token that passes the expiry and `user:profile` scope checks, with a 2-second budget, and later refreshes reuse its result. A failed lookup is not retried until the token changes or CodexBar restarts (previously every 15 minutes), and the first refresh after launch or a token change may show no email until the next refresh (upstream v0.76.0, #4538).
- **Claude**: OAuth usage now shows saved limit-reset credits, as the Web source already did. The usage request opts in with `cedar_ember=1` and identifies as the Claude CLI (`claude-cli/<version> (external, cli)`); when Claude rejects that optional query with HTTP 400 or a 403 that does not name the missing `user:profile` scope, it retries once without it, as `claude-code/<version>`, so quota and Extra usage still appear. The version is read from `claude --version`, or 2.1.0 when it cannot be read. Missing-scope 403, 401 and 429 keep their normal handling. The cached usage body keeps reset counts and dates but never the grant identifiers (upstream 0.72.0, #3895, #4232).
- Usage forecasts: an older session reset time that later moved backward no longer hides the estimated session quotas left and windows until reset for Codex and Claude. The estimator starts a new history segment at the correction and still needs three recent samples (upstream #4540).
- OpenCode Go: include OpenCode v2 session history in local cost and token totals, without counting migrated messages twice (upstream v0.77.0 #4526).
Expand Down
36 changes: 23 additions & 13 deletions rust/src/providers/claude/oauth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,17 +633,20 @@ impl ClaudeOAuthFetcher {
credentials: ClaudeOAuthCredentials,
gate_key: &str,
) -> Result<ProviderFetchResult, ProviderError> {
let fingerprint = usage_gate::token_fingerprint(&credentials.access_token);
// Upstream validates the token before its profile lookup, so an expired
// or scope-less token sends no /profile request.
Self::check_usage_credentials(&credentials)?;
// Started before the usage request so a quick profile answer can still
// label this refresh; the usage request never waits for it.
let _ = profile::display_email(&self.client, &credentials.access_token, &fingerprint);
let usage_response = self.fetch_usage(&credentials, gate_key).await?;
let mut usage = self.build_usage_snapshot(&usage_response, &credentials);
let spend_cost = apply_spend_only_extra_usage(&mut usage, &usage_response);
// Header email only; a profile failure leaves the usage result intact.
// Header email only; a missing or failed profile leaves usage intact.
if usage.account_email.is_none()
&& let Some(email) = profile::fetch_email(
&self.client,
&credentials.access_token,
&usage_gate::token_fingerprint(&credentials.access_token),
)
.await
&& let Some(email) =
profile::display_email(&self.client, &credentials.access_token, &fingerprint)
{
usage = usage.with_email(email);
}
Expand Down Expand Up @@ -784,12 +787,9 @@ impl ClaudeOAuthFetcher {
}
}

/// Fetch usage data using OAuth credentials
pub async fn fetch_usage(
&self,
credentials: &ClaudeOAuthCredentials,
gate_key: &str,
) -> Result<OAuthUsageResponse, ProviderError> {
/// Expiry and `user:profile` scope checks that run before any request for
/// the token, including the profile lookup.
fn check_usage_credentials(credentials: &ClaudeOAuthCredentials) -> Result<(), ProviderError> {
if credentials.is_expired() {
return Err(ProviderError::OAuthExpired(
"OAuth token expired. Run `claude` to refresh.".to_string(),
Expand All @@ -804,6 +804,16 @@ impl ClaudeOAuthFetcher {
Self::scope_recovery_message()
)));
}
Ok(())
}

/// Fetch usage data using OAuth credentials
pub async fn fetch_usage(
&self,
credentials: &ClaudeOAuthCredentials,
gate_key: &str,
) -> Result<OAuthUsageResponse, ProviderError> {
Self::check_usage_credentials(credentials)?;

if let Some(result) = Self::gate_precheck(self.usage_gate, usage_gate::now_ms(), gate_key) {
return result;
Expand Down
Loading