Skip to content

Read GitHub rate-limit headers whatever their casing [patch] - #390

Merged
matt-edmondson merged 3 commits into
mainfrom
fix/389-rate-limit-header-case
Oct 31, 2026
Merged

matt-edmondson merged 3 commits into
mainfrom
fix/389-rate-limit-header-case

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #389

What was wrong

Octokit keys IResponse.Headers case-sensitively, using each name as it arrived on the wire. GitHub sends x-ratelimit-remaining and x-ratelimit-reset in lowercase, and proxies often re-case them as X-Ratelimit-*. ParseRateLimitResetTime and IsRateLimitResponse looked up the exact spelling X-RateLimit-*, so they missed both. A spent primary budget then fell through to the one-minute secondary backoff and was retried every minute until the real reset.

Change

  • Adds GitHub.TryGetHeader. It tries the exact key first, then falls back to an OrdinalIgnoreCase scan.
  • Routes Retry-After, X-RateLimit-Remaining and X-RateLimit-Reset through it, in both ParseRateLimitResetTime and IsRateLimitResponse.

Tests

New cases in GitHubRateLimitResetTests:

  • A spent budget with lowercase or X-Ratelimit-* headers waits for the reset (now+45 min), not now+1 min.
  • A lowercase retry-after is honoured.
  • A 403 with a lowercase or mixed-case remaining: 0, whose message doesn't mention a rate limit, counts as a rate limit.

With the fix reverted, all 5 new cases fail. With it, the full BuildMonitor.Test suite passes (156/156).


Generated by Claude Code

Octokit keys response headers case-sensitively, as they arrived on the
wire, and GitHub sends x-ratelimit-remaining and x-ratelimit-reset in
lowercase. The exact-case X-RateLimit-* lookups missed them, so a spent
primary budget fell through to the one-minute secondary backoff and was
retried every minute until it reset.

Look Retry-After and the X-RateLimit-* headers up through a helper that
ignores case, as HTTP does. Fixes #389.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW4T6sUBiRNom98NuYi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW4T6sUBiRNom98NuYi
…t always exits

SonarCloud S1751 rated the Where loop, which returned on its first
iteration, as a reliability bug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW4T6sUBiRNom98NuYi
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 62fe896 into main Oct 31, 2026
16 checks passed
@matt-edmondson
matt-edmondson deleted the fix/389-rate-limit-header-case branch October 31, 2026 03:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A spent GitHub rate limit is retried every minute instead of waiting for its reset: X-RateLimit-* headers are looked up case-sensitively

2 participants