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
AbuseRateLimitError.Error() does not include the RetryAfter duration, even when it is populated from the Retry-After HTTP header. Callers that surface .Error() to users, logs, or downstream systems silently lose actionable retry information.
Motivation
This gap has existed since AbuseRateLimitError was introduced in 2016 (PR #441), but was relatively minor because Go callers can use errors.As to inspect .RetryAfter directly from the struct — the string form is mostly used for logging.
It has become more impactful with the rise of AI coding agents. The github-mcp-server exposes GitHub API operations as MCP tools, and AI agents receive tool results as plain strings — there is no way for the model to inspect the underlying struct. When an agent hits a secondary rate limit, it currently sees no retry duration and must choose between retrying immediately (worsening the situation) or waiting an arbitrary amount of time. Including the duration in .Error() gives agent frameworks and models the information needed to back off correctly.
When GitHub returns a Retry-After: 60 header, RetryAfter is populated, but .Error() produces only:
GET https://api.github.com/search/code: 403 You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later.
The retry duration is silently dropped — the only way to recover it is to errors.As to *AbuseRateLimitError and read .RetryAfter directly.
Contrast with RateLimitError
RateLimitError.Error() does include timing via formatRateReset:
GET https://api.github.com/search/code: 403 You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later. [retry after 60s]
This is a non-breaking additive change to the string output, and makes AbuseRateLimitError consistent with RateLimitError.
Note
This issue was drafted with the assistance of GitHub Copilot.
Summary
AbuseRateLimitError.Error()does not include theRetryAfterduration, even when it is populated from theRetry-AfterHTTP header. Callers that surface.Error()to users, logs, or downstream systems silently lose actionable retry information.Motivation
This gap has existed since
AbuseRateLimitErrorwas introduced in 2016 (PR #441), but was relatively minor because Go callers can useerrors.Asto inspect.RetryAfterdirectly from the struct — the string form is mostly used for logging.It has become more impactful with the rise of AI coding agents. The github-mcp-server exposes GitHub API operations as MCP tools, and AI agents receive tool results as plain strings — there is no way for the model to inspect the underlying struct. When an agent hits a secondary rate limit, it currently sees no retry duration and must choose between retrying immediately (worsening the situation) or waiting an arbitrary amount of time. Including the duration in
.Error()gives agent frameworks and models the information needed to back off correctly.Current behavior
When GitHub returns a
Retry-After: 60header,RetryAfteris populated, but.Error()produces only:The retry duration is silently dropped — the only way to recover it is to
errors.Asto*AbuseRateLimitErrorand read.RetryAfterdirectly.Contrast with
RateLimitErrorRateLimitError.Error()does include timing viaformatRateReset:Which produces, e.g.:
Suggested fix to
AbuseRateLimitErrorWhich would produce, e.g.:
This is a non-breaking additive change to the string output, and makes
AbuseRateLimitErrorconsistent withRateLimitError.Note
This issue was drafted with the assistance of GitHub Copilot.