fix(ratelimit): clear window token counters on reset from response headers

UpdateFromHeaders was silently updating ResetsAt without clearing token
counters. When a window rolled over, the poll method would see ResetsAt
already updated and skip the reset. Extract setResetTime helper used by
both code paths.
This commit is contained in:
Alexander
2026-04-14 13:37:06 +02:00
parent e8af26d626
commit 744abc1d24
+12 -11
View File
@@ -71,7 +71,7 @@ func (t *Tracker) UpdateFromHeaders(h http.Header) {
} }
if v := h.Get("Anthropic-Ratelimit-Unified-5h-Reset"); v != "" { if v := h.Get("Anthropic-Ratelimit-Unified-5h-Reset"); v != "" {
if ts, err := strconv.ParseInt(v, 10, 64); err == nil { if ts, err := strconv.ParseInt(v, 10, 64); err == nil {
t.fiveHour.ResetsAt = time.Unix(ts, 0).UTC().Truncate(time.Minute) t.setResetTime(&t.fiveHour, time.Unix(ts, 0).UTC().Truncate(time.Minute), "5h")
} }
} }
if v := h.Get("Anthropic-Ratelimit-Unified-7d-Utilization"); v != "" { if v := h.Get("Anthropic-Ratelimit-Unified-7d-Utilization"); v != "" {
@@ -81,7 +81,7 @@ func (t *Tracker) UpdateFromHeaders(h http.Header) {
} }
if v := h.Get("Anthropic-Ratelimit-Unified-7d-Reset"); v != "" { if v := h.Get("Anthropic-Ratelimit-Unified-7d-Reset"); v != "" {
if ts, err := strconv.ParseInt(v, 10, 64); err == nil { if ts, err := strconv.ParseInt(v, 10, 64); err == nil {
t.sevenDay.ResetsAt = time.Unix(ts, 0).UTC().Truncate(time.Minute) t.setResetTime(&t.sevenDay, time.Unix(ts, 0).UTC().Truncate(time.Minute), "7d")
} }
} }
} }
@@ -190,24 +190,25 @@ func (t *Tracker) updateWindow(w *Window, rl *RateLimit, name string) {
if rl.ResetsAt != nil { if rl.ResetsAt != nil {
parsed, err := time.Parse(time.RFC3339Nano, *rl.ResetsAt) parsed, err := time.Parse(time.RFC3339Nano, *rl.ResetsAt)
if err != nil { if err != nil {
// Fallback to RFC3339 without fractional seconds
parsed, err = time.Parse(time.RFC3339, *rl.ResetsAt) parsed, err = time.Parse(time.RFC3339, *rl.ResetsAt)
} }
parsed = parsed.UTC().Truncate(time.Minute) if err == nil {
if err == nil && parsed != w.ResetsAt && !w.ResetsAt.IsZero() { t.setResetTime(w, parsed.UTC().Truncate(time.Minute), name)
// Window reset detected — zero token counters }
}
}
func (t *Tracker) setResetTime(w *Window, newReset time.Time, name string) {
if !w.ResetsAt.IsZero() && newReset != w.ResetsAt {
log.Info(). log.Info().
Str("window", name). Str("window", name).
Int64("prev_tokens_in", w.TokensIn.Load()). Int64("prev_tokens_in", w.TokensIn.Load()).
Int64("prev_tokens_out", w.TokensOut.Load()). Int64("prev_tokens_out", w.TokensOut.Load()).
Time("old_reset", w.ResetsAt). Time("old_reset", w.ResetsAt).
Time("new_reset", parsed). Time("new_reset", newReset).
Msg("window reset detected") Msg("window reset detected")
w.TokensIn.Store(0) w.TokensIn.Store(0)
w.TokensOut.Store(0) w.TokensOut.Store(0)
} }
if err == nil { w.ResetsAt = newReset
w.ResetsAt = parsed
}
}
} }