menubar: honor Retry-After on Claude quota 429s, back off failed refreshes#702
Conversation
|
Verification upgrade: a standalone Swift 6.3.3 toolchain is now set up on the dev machine, so |
iamtoruk
left a comment
There was a problem hiding this comment.
Verified locally: swift build green, the full test run passes including the new XCTest rate-limit suite alongside the Swift Testing suites, and both core behaviors are mutation-pinned (removing the Retry-After header precedence fails two tests, reverting block escalation to overwrite fails one). The three-cause diagnosis matches the code and the #701 repro. Not driven against a live rate-limited account, which the unit pins and the reporter's persisted-defaults evidence cover as well as possible.
Problem
The Claude Plan section can get stuck in a perpetual "rate-limited, retrying" loop (#701, reproduced on a real user machine — persisted
blockedUntilin defaults — while CodexBar polls the identical endpoint on the same account without issue). Three compounding causes:retry_afterfrom the JSON body only; the standardRetry-Afterresponse header is never read, so recovery nearly always falls back to a flat 300s and re-probes into a still-open server window, overwriting (not escalating) the block.Fix
Retry-Afterheader first (delta-seconds and HTTP-date forms, POSIX locale, UTC), then the bodyretry_after, then the 300s default — and the persisted block is nowmax(existing, new)so repeated 429s escalate instead of resetting. Both parsing and escalation are extracted as pure static functions.store.refreshSubscription()directly, so interactive refreshes can't run concurrently with timer fetches. Retry therefore triggers the same full refresh as "Refresh Now" — heavier than a bare quota fetch, but one consistent code path.Tests
New
mac/Tests/CodeBurnMenubarTests/ClaudeRateLimitTests.swift: header delta-seconds (takes precedence over body), HTTP-date parsing against an injected clock, malformed-header → body fallback → 300s default, block escalation never shrinks, and backoff growth/cap/jitter bounds for the extracted pure function.Verification
swiftc -parseclean on all five touched files;git diff --checkclean.swift build/swift testcannot run on the development machine (SwiftPM manifest linking broken machine-wide, fails on unmodified main too) — CI/maintainer verification is the build gate, as with #698.Fixes #701