From 9cc4018a3775f75798046896790e64f544fd25fd Mon Sep 17 00:00:00 2001 From: dalamudx Date: Wed, 30 Sep 2026 00:29:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(claude-code):=20=E9=A2=9D=E5=BA=A6=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=90=BA=E5=B8=A6=20x-app/claude-cli=20UA=20=E4=BB=A5?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=20cedar=5Fember=EF=BC=8C=E5=B9=B6=E5=9C=A8?= =?UTF-8?q?=E6=97=A0=E9=87=8D=E7=BD=AE=E6=9C=BA=E4=BC=9A=E6=97=B6=E6=98=BE?= =?UTF-8?q?=E5=BC=8F=E6=B8=85=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/aether-admin/src/provider/quota.rs | 50 +++++++++++++++++-- .../pool/src/providers/claude_code.rs | 4 +- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/crates/aether-admin/src/provider/quota.rs b/crates/aether-admin/src/provider/quota.rs index 3778bdccd..7e4b5174b 100644 --- a/crates/aether-admin/src/provider/quota.rs +++ b/crates/aether-admin/src/provider/quota.rs @@ -456,12 +456,15 @@ pub fn parse_claude_code_oauth_usage_response( bucket.insert(format!("{prefix}_reset_at"), serde_json::json!(reset_at)); } } - if let Some(reset_credits) = parse_claude_code_reset_credits(root, updated_at_unix_secs) { - bucket.insert("reset_credits".to_string(), reset_credits); - } if bucket.is_empty() { return None; } + // Explicit null so a refresh overwrites (rather than keeps) credits that were used up. + bucket.insert( + "reset_credits".to_string(), + parse_claude_code_reset_credits(root, updated_at_unix_secs) + .unwrap_or(serde_json::Value::Null), + ); bucket.insert( "updated_at".to_string(), serde_json::json!(updated_at_unix_secs), @@ -7934,6 +7937,45 @@ mod claude_code_quota_tests { assert!(!credits.to_string().contains("launch")); } + #[test] + fn real_cedar_ember_response_survives_metadata_redaction() { + // Shape captured from a live Claude Pro account (unrelated fields trimmed). + let parsed = parse_claude_code_oauth_usage_response( + &json!({ + "five_hour": {"utilization": 32.0, "resets_at": "2026-09-29T19:19:59.933008+00:00"}, + "cedar_ember": { + "eligible": true, + "at_limit": false, + "grants": [{ + "id": "opus55-launch-promax-20260921", + "label": "Claude Opus 5.5 launch", + "resets_total": 1, + "resets_left": 1, + "starts_at": "2026-09-22T16:00:00+00:00", + "ends_at": "2026-10-22T16:00:00+00:00", + "clears": ["five_hour", "seven_day"], + "paused": false, + "usable_now": true, + "use_requires_limit": false + }], + "next_grant_id": "opus55-launch-promax-20260921" + } + }), + 1_790_699_000, + ) + .expect("usage should parse"); + assert_eq!(parsed["reset_credits"]["available_count"], json!(1)); + + let safe = crate::provider::redaction::admin_provider_upstream_metadata_safe_json(Some( + &json!({ "claude_code": parsed }), + )); + assert_eq!( + safe["claude_code"]["reset_credits"]["available_count"], + json!(1), + "redaction dropped reset_credits: {safe}" + ); + } + #[test] fn cedar_ember_null_or_empty_omits_reset_credits() { let parsed = parse_claude_code_oauth_usage_response( @@ -7941,7 +7983,7 @@ mod claude_code_quota_tests { 1, ) .expect("usage should parse"); - assert!(parsed.get("reset_credits").is_none()); + assert!(parsed["reset_credits"].is_null()); } #[test] diff --git a/crates/aether-provider/pool/src/providers/claude_code.rs b/crates/aether-provider/pool/src/providers/claude_code.rs index 27bac2186..195dd40cb 100644 --- a/crates/aether-provider/pool/src/providers/claude_code.rs +++ b/crates/aether-provider/pool/src/providers/claude_code.rs @@ -11,7 +11,7 @@ use crate::quota_refresh::ProviderPoolQuotaRequestSpec; pub const CLAUDE_CODE_OAUTH_USAGE_URL: &str = "https://api.anthropic.com/api/oauth/usage?cedar_ember=1&skip_spend=1"; pub const CLAUDE_CODE_OAUTH_BETA: &str = "oauth-2025-04-20"; -pub const CLAUDE_CODE_USAGE_USER_AGENT: &str = "claude-code/2.1.284"; +pub const CLAUDE_CODE_USAGE_USER_AGENT: &str = "claude-cli/2.1.284 (external, cli)"; #[derive(Debug, Clone, Default)] pub struct ClaudeCodeProviderPoolAdapter; @@ -58,6 +58,8 @@ pub fn build_claude_code_pool_quota_request( "anthropic-beta".to_string(), CLAUDE_CODE_OAUTH_BETA.to_string(), ), + // The reset-credit (`cedar_ember`) block is only returned to first-party CLI callers. + ("x-app".to_string(), "cli".to_string()), ( "user-agent".to_string(), CLAUDE_CODE_USAGE_USER_AGENT.to_string(),