Skip to content

🎯 Handle HTTP-date format in Retry-After header for Secondary Rate Limits #1

Description

@Tylerx3udv

📝 Description

When GitHub's API triggers a secondary rate limit, it responds with a 403 Forbidden status code and includes a Retry-After header. According to the HTTP specification and GitHub's API behavior, this header can be formatted in two ways:

  1. As a non-negative decimal integer indicating the number of seconds to delay (e.g., 120).
  2. As an HTTP-date (RFC 7231 format, e.g., Wed, 21 Oct 2015 07:28:00 GMT).

Currently, the go-github client fails to parse the Retry-After header when it is returned as an HTTP-date. As a result, the parsed error (typically AbuseRateLimitError or a generic rate limit error) does not expose the correct retry duration to the caller. This makes it impossible for client applications to implement reliable, automatic retry middleware based on the returned error.

🎯 Acceptance Criteria

  • The client must successfully parse the Retry-After header when it is formatted as an integer (seconds).
  • The client must successfully parse the Retry-After header when it is formatted as an HTTP-date (RFC 1123 / RFC 7231 format).
  • The parsed retry duration must be exposed in the returned error struct (e.g., AbuseRateLimitError.RetryAfter or equivalent) as a helper field (like time.Duration or *time.Time).
  • If parsing fails or the header is missing, the client should degrade gracefully (e.g., returning a zero duration or nil time) without panicking.
  • The solution must be backward-compatible with existing error structures where possible.

🛠️ Technical Specifications & Context

  • Location of Logic: The response parsing and error handling logic resides in github/github.go (specifically within the CheckResponse function which inspects HTTP status codes and constructs error types like AbuseRateLimitError).
  • Error Definition: Check github/github.go for the definition of AbuseRateLimitError. It should have a field representing the retry delay (e.g., RetryAfter *time.Duration or similar).
  • Parsing Implementation:
    • When extracting the Retry-After header:
      1. Attempt to parse it as an integer using strconv.Atoi(). If successful, convert it to a time.Duration (seconds).
      2. If integer parsing fails, attempt to parse it as an HTTP-date using net/http's http.ParseTime() or time.Parse(time.RFC1123, ...) / time.RFC1123A.
      3. If parsed as a timestamp, calculate the duration relative to the current time (time.Until(parsedTime)) or expose the absolute target time.

Example helper structure:

// In github/github.go (or equivalent error definition file)
type AbuseRateLimitError struct {
    Response   *http.Response
    Message    string         `json:"message"`
    DocumentationURL string   `json:"documentation_url"`
    RetryAfter *time.Duration // Should represent the parsed duration
}

🧪 Verification & Testing

  • Unit Tests: Add test cases in github/github_test.go (or the corresponding test file for response parsing):
    • Test CheckResponse with a 403 status code and Retry-After: 60. Verify that the returned error contains a retry duration of 60 seconds.
    • Test CheckResponse with a 403 status code and Retry-After: <Future HTTP-Date> (e.g., a date 5 minutes in the future). Verify that the returned error contains a retry duration of approximately 5 minutes.
    • Test CheckResponse with an invalid or missing Retry-After header to ensure no panics occur and a default/nil value is returned.

Opire Bounty


This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️‍♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim #1 either in the PR description or in a PR's comment.

🪙 Also, everyone can tip any user commenting /tip 20 @Tylerx3udv (replace 20 with the amount, and @Tylerx3udv with the user to tip).

📖 If you want to learn more, check out our documentation.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions