Skip to content

Commit 668e164

Browse files
StarryKiraclaude
andcommitted
fix(usage): use real reset header for session window instead of prediction
The 5h window reset time displayed for Setup Token accounts was inaccurate because UpdateSessionWindow predicted the window end as "current hour + 5h" instead of reading the actual `anthropic-ratelimit-unified-5h-reset` response header. This caused the countdown to differ from the official Claude page. Backend: parse the reset header (Unix timestamp) and use it as the real window end, falling back to the hour-truncated prediction only when the header is absent. Also correct stale predictions when a subsequent request provides the real reset time. Frontend: add a reactive 60s timer so the reset countdown in UsageProgressBar ticks down in real-time instead of freezing at the initial value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f42c8f2 commit 668e164

3 files changed

Lines changed: 395 additions & 7 deletions

File tree

backend/internal/service/ratelimit_service.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,16 +1051,32 @@ func (s *RateLimitService) UpdateSessionWindow(ctx context.Context, account *Acc
10511051
var windowStart, windowEnd *time.Time
10521052
needInitWindow := account.SessionWindowEnd == nil || time.Now().After(*account.SessionWindowEnd)
10531053

1054-
if needInitWindow && (status == "allowed" || status == "allowed_warning") {
1055-
// 预测时间窗口:从当前时间的整点开始,+5小时为结束
1056-
// 例如:现在是 14:30,窗口为 14:00 ~ 19:00
1054+
// 优先使用响应头中的真实重置时间(比预测更准确)
1055+
if resetStr := headers.Get("anthropic-ratelimit-unified-5h-reset"); resetStr != "" {
1056+
if ts, err := strconv.ParseInt(resetStr, 10, 64); err == nil {
1057+
end := time.Unix(ts, 0)
1058+
// 窗口需要初始化,或者真实重置时间与已存储的不同,则更新
1059+
if needInitWindow || account.SessionWindowEnd == nil || !end.Equal(*account.SessionWindowEnd) {
1060+
start := end.Add(-5 * time.Hour)
1061+
windowStart = &start
1062+
windowEnd = &end
1063+
slog.Info("account_session_window_from_header", "account_id", account.ID, "window_start", start, "window_end", end, "status", status)
1064+
}
1065+
}
1066+
}
1067+
1068+
// 回退:如果没有真实重置时间且需要初始化窗口,使用预测
1069+
if windowEnd == nil && needInitWindow && (status == "allowed" || status == "allowed_warning") {
10571070
now := time.Now()
10581071
start := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
10591072
end := start.Add(5 * time.Hour)
10601073
windowStart = &start
10611074
windowEnd = &end
10621075
slog.Info("account_session_window_initialized", "account_id", account.ID, "window_start", start, "window_end", end, "status", status)
1063-
// 窗口重置时清除旧的 utilization,避免残留上个窗口的数据
1076+
}
1077+
1078+
// 窗口重置时清除旧的 utilization,避免残留上个窗口的数据
1079+
if windowEnd != nil && needInitWindow {
10641080
_ = s.accountRepo.UpdateExtra(ctx, account.ID, map[string]any{
10651081
"session_window_utilization": nil,
10661082
})

0 commit comments

Comments
 (0)