can1357/oh-my-pi · error · GitHubError
GitHubError(resp.status_code, str(msg), retry_after=retry_af
Error message
GitHubError(resp.status_code, str(msg), retry_after=retry_after)
What it means
GitHubError raised by GitHubClient._check whenever the GitHub API responds with status >= 400. It extracts the `message` field from the JSON error body (falling back to raw text) and attaches any `Retry-After` header so callers can rate-limit-aware retry.
Source
Thrown at python/robomp/src/github_client.py:267
def _async_client(self) -> httpx.AsyncClient:
return httpx.AsyncClient(
base_url=GITHUB_API,
headers=self._headers,
transport=self._transport, # type: ignore[arg-type]
timeout=httpx.Timeout(30.0, connect=10.0),
follow_redirects=True,
)
# ---- request helpers ----
def _check(self, resp: httpx.Response) -> Any:
if resp.status_code >= 400:
retry_after = _parse_retry_after(resp)
try:
msg = resp.json().get("message", resp.text)
except Exception:
msg = resp.text
raise GitHubError(resp.status_code, str(msg), retry_after=retry_after)
if resp.status_code >= 300:
# Redirect we couldn't (or weren't asked to) follow. GitHub uses 301
# for transferred repos / issues. Surface as a normal error so host
# tools map it to RpcCommandError instead of mis-parsing the body.
location = resp.headers.get("location", "")
raise GitHubError(
resp.status_code,
f"unexpected redirect to {location!r}; resource may have moved",
)
if resp.status_code == 204 or not resp.content:
return None
return resp.json()
_TRANSIENT_RETRY_DELAYS = (1.0, 3.0, 10.0)
"""Backoff schedule for transient connection/timeout/5xx errors."""
_TRANSIENT_STATUSES = frozenset({500, 502, 503, 504})
"""Upstream statuses treated as transient — retried for idempotent methods only."""View on GitHub (pinned to 9690622007)
Solutions
- Read the embedded status and message to identify the specific cause
- If retry_after is set, wait that many seconds before retrying
- Verify the token is valid and has required scopes (`gh auth status`, check GH_TOKEN env)
- Check rate-limit headers (`x-ratelimit-remaining`) and back off / authenticate requests
Example fix
// before: unauthenticated burst calls
for r in refs: client.request("GET", f"/repos/{r}")
// after: catch and honor retry_after
try:
data = client.request("GET", path)
except GitHubError as e:
if e.retry_after:
time.sleep(e.retry_after)
else:
raise Defensive patterns
Strategy: retry
Validate before calling
import os
if not os.environ.get("GITHUB_TOKEN"):
raise RuntimeError("GITHUB_TOKEN must be set for API calls") Type guard
def is_retryable(e: GitHubError) -> bool:
return e.retry_after is not None or (500 <= e.status_code < 600) Try / catch
try:
data = await client.request("GET", path)
except GitHubError as e:
if e.retry_after:
await asyncio.sleep(e.retry_after)
data = await client.request("GET", path)
elif 500 <= e.status_code < 600:
await asyncio.sleep(backoff)
data = await client.request("GET", path)
else:
raise Prevention
- Always authenticate requests to raise the rate limit
- Track x-ratelimit-remaining and back off before exhausting
- Check `gh auth status` and token scopes when 401/403 appears
- Validate repo/issue names client-side to avoid 404s
When it happens
Trigger: Any API call (request_sync, request, _request_text_tail) receiving 4xx/5xx: bad token (401), missing scopes (403), rate limit (403/429 with Retry-After), nonexistent repo/issue (404), validation failure (422), GitHub 5xx.
Common situations: Expired GITHUB_TOKEN, hitting the 5000 req/hour REST limit, typos in repo names, forbidden access to private repos, secondary rate limits during bulk operations.
Related errors
- Invalid GitHub release metadata
- unexpected redirect to {location!r}; resource may have moved
- An unknown error occurred
- Devin API error ${response.status} ${response.statusText}: $
- Devin AssignModel error ${response.status} ${response.status
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/62ff4eeb0e4bd15f.
Report an issue: GitHub.