chatboxai/chatbox · warning · GitHubApiError
GitHub API rate limit exceeded. Try again later.
Error message
GitHub API rate limit exceeded. Try again later.
What it means
GitHubApiError with status 403, thrown by githubFetch when GitHub responds 403. The code attributes all 403s to rate-limiting, which is the dominant cause for unauthenticated GitHub API calls (60 req/hour/IP) but note 403 can also mean a forbidden private repo or an abuse-detection block.
Source
Thrown at src/main/skills/github-fetcher.ts:90
}
async function githubFetch<T>(url: string): Promise<T> {
const cached = getCached<T>(url)
if (cached !== undefined) return cached
const response = await fetch(url, {
headers: {
'User-Agent': USER_AGENT,
Accept: 'application/vnd.github.v3+json',
},
})
if (!response.ok) {
if (response.status === 404) {
throw new GitHubApiError(`Not found: ${url}`, 404)
}
if (response.status === 403) {
throw new GitHubApiError('GitHub API rate limit exceeded. Try again later.', 403)
}
throw new GitHubApiError(`GitHub API error: ${response.status} ${response.statusText}`, response.status)
}
const data = (await response.json()) as T
setCache(url, data)
return data
}
async function fetchRepoTree(owner: string, repo: string): Promise<GitHubTreeResponse | null> {
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/git/trees/HEAD?recursive=1`
const result = await githubFetch<GitHubTreeResponse>(url)
if (!Array.isArray(result?.tree)) {
log.warn(`Unexpected tree response shape for ${owner}/${repo}`)
return null
}
if (result.truncated) {
log.warn(`Tree listing truncated for ${owner}/${repo}`)View on GitHub (pinned to 81571269ad)
Solutions
- Set a GITHUB token (raises limit to 5000/hour) for skill syncing, especially in shared/CI environments.
- Honor Retry-After / X-RateLimit-Reset headers and back off instead of retrying immediately.
- Rely on getCached/setCache to avoid repeated fetches of the same URL within the TTL.
- Distinguish true rate-limit (X-RateLimit-Remaining: 0) from forbidden-resource 403 to give accurate guidance.
Example fix
// before
if (response.status === 403) throw new GitHubApiError('GitHub API rate limit exceeded. Try again later.', 403)
// after: honor reset header
if (response.status === 403) {
const reset = Number(response.headers.get('x-ratelimit-reset')) * 1000
throw new GitHubApiError(`GitHub API rate limit exceeded. Resets at ${new Date(reset).toISOString()}`, 403)
} Defensive patterns
Strategy: retry
Validate before calling
if (!process.env.GITHUB_TOKEN && sharedEnvironment) warn('Unauthenticated GitHub budget is 60/hour/IP') Type guard
function isGitHubRateLimited(e: unknown): e is GitHubApiError { return e instanceof GitHubApiError && e.status === 403 } Try / catch
try { return await githubFetch(url) } catch (e) {
if (isGitHubRateLimited(e)) { const reset = getRateLimitReset(); await delay(until reset); return githubFetch(url) }
throw e
} Prevention
- Set GITHUB_TOKEN (5000/hour) for skill syncing, especially on shared IPs.
- Rely on getCached/setCache to avoid repeat fetches.
- Honor X-RateLimit-Reset/Retry-After instead of immediate retries.
- Distinguish rate-limit 403 from forbidden-resource 403.
When it happens
Trigger: Unauthenticated requests to api.github.com exceeding 60/hour per IP; a burst of skill-sync fetches (repo tree + per-file content) blowing the budget; secondary rate limits tripped by concurrent requests; legitimate 403 on a forbidden resource misclassified as rate-limit.
Common situations: Skill catalog refresh in a CI/shared-IP environment where the 60/hour budget is shared; recursive tree fetches plus many blob fetches; many users behind one NAT syncing at once; no GITHUB token configured.
Related errors
- Not found: ${url}
- GitHub API error: ${response.status} ${response.statusText}
- Not found
- Failed to start preview server
- invalid file entry for "${name}"
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/f9ce6519d91a92fd.
Report an issue: GitHub.