You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A GitHub secondary rate limit pauses polling for 10 minutes instead of the ~60 s Retry-After, and an Azure DevOps 429's Retry-After is ignored #385
GitHub.IsRateLimitResponse (BuildMonitor/Providers/GitHub.cs ~L873) correctly classifies a secondary (abuse) rate limit through its "rate limit" message, even when X-RateLimit-Remaining is far from 0. ParseRateLimitResetTime (~L857) then reads only X-RateLimit-Reset. On a secondary limit that header is the reset of the primary hourly window, often 30–60 minutes away, and is unrelated to how long GitHub wants the client to back off. The Retry-After header that GitHub sends with secondary limits (typically 60 s) is never read. GetRateLimitWaitTime() then waits min(reset − now, 10 min).
Azure DevOps. The 429 handler (BuildMonitor/Providers/AzureDevOps.cs ~L460) calls OnRateLimitExceeded() with no reset time, so the Retry-After that ADO sends is ignored too. The next request only gets the generic backoff, which can be well short of what the server asked for, so the client keeps getting throttled.
Failure scenario
The review that found this verified the GitHub case. A 403 SecondaryRateLimitExceededException with these headers:
X-RateLimit-Remaining: 4000
X-RateLimit-Reset: now + 45 min
Retry-After: 60
leaves the provider at Status = RateLimited with GetRateLimitWaitTime() == 00:10:00. Every GitHub request then waits 10 minutes rather than about 60 s, even though the hourly budget is nearly full. While the update pass is stuck (see the companion issue on the shared update loop), every other provider stalls with it.
Suggested fix
GitHub: when Retry-After is present, use now + Retry-After as the reset time. Use X-RateLimit-Reset only when X-RateLimit-Remaining == 0. Otherwise, for a secondary limit with no Retry-After, back off about 60 s, as GitHub's guidance says.
Azure DevOps: read Retry-After from the VssServiceResponseException's response (or the X-RateLimit-Reset/Retry-After headers ADO returns) and pass it to OnRateLimitExceeded(...).
Acceptance criteria
A secondary-limit 403 with Retry-After: 60 and a full primary budget yields a wait of about 60 s, not 10 min.
A primary-limit 403 (Remaining: 0) still waits until X-RateLimit-Reset.
An ADO 429 with Retry-After: N waits about N seconds.
Unit tests cover each case through ParseRateLimitResetTime or its replacement.
Related: #341 and #331 cover the pacing and budget model, not how the reset time is derived.
The GitHub half is in #388.Retry-After is honoured, X-RateLimit-Reset is used only when Remaining: 0, and otherwise the backoff is 60 s. Tests cover each acceptance case.
The Azure DevOps half is not done, and the suggested fix doesn't work as written.VssServiceResponseException exposes only HttpStatusCode and the message (checked by reflecting over the shipped Microsoft.VisualStudio.Services.WebApi). There is no response object and no headers, so Retry-After can't be read off the exception in the 429 catch.
What it needs instead:
A DelegatingHandler passed to new VssConnection(uri, credentials, handlers) in AzureDevOps.CreateSession. It records the Retry-After of the last 429 into a sink the provider owns.
The 429 catch in MakeAzureDevOpsRequestAsync passes that value to OnRateLimitExceeded(...).
Before relying on it, confirm whether the client's own VssHttpRetryMessageHandler already retries 429s internally, and how the custom handlers are ordered relative to it.
That can't be verified here: building a session authenticates against dev.azure.com, and AzureDevOpsSessionTests documents the same limit. The recorder itself can be unit-tested with a fake inner handler, but the wiring can't. Leaving the issue open and unassigned for that half. #388 deliberately says "Part of", so merging it won't close this issue.
Activity
matt-edmondson commentedon Oct 29, 2026
Triage
The GitHub half is in #388.
Retry-Afteris honoured,X-RateLimit-Resetis used only whenRemaining: 0, and otherwise the backoff is 60 s. Tests cover each acceptance case.The Azure DevOps half is not done, and the suggested fix doesn't work as written.
VssServiceResponseExceptionexposes onlyHttpStatusCodeand the message (checked by reflecting over the shippedMicrosoft.VisualStudio.Services.WebApi). There is no response object and no headers, soRetry-Aftercan't be read off the exception in the 429 catch.What it needs instead:
DelegatingHandlerpassed tonew VssConnection(uri, credentials, handlers)inAzureDevOps.CreateSession. It records theRetry-Afterof the last 429 into a sink the provider owns.MakeAzureDevOpsRequestAsyncpasses that value toOnRateLimitExceeded(...).VssHttpRetryMessageHandleralready retries 429s internally, and how the custom handlers are ordered relative to it.That can't be verified here: building a session authenticates against dev.azure.com, and
AzureDevOpsSessionTestsdocuments the same limit. The recorder itself can be unit-tested with a fake inner handler, but the wiring can't. Leaving the issue open and unassigned for that half. #388 deliberately says "Part of", so merging it won't close this issue.Generated by Claude Code