chatboxai/chatbox · error · GitHubApiError
GitHub API error: ${response.status} ${response.statusText}
Error message
GitHub API error: ${response.status} ${response.statusText} What it means
Thrown by githubFetch() as a generic catch-all when the GitHub REST API (api.github.com) returns a non-OK status that is not 404 or 403. The error carries the HTTP status code and statusText so callers can distinguish transient 5xx failures from auth/validation problems (401, 422, 451). githubFetch is the shared gateway for all GitHub data access in the skills module (repo trees, contents, raw file fetches) and is called without authentication, so it is subject to the 60 req/hr unauthenticated rate limit.
Source
Thrown at src/main/skills/github-fetcher.ts:92
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}`)
return null
}View on GitHub (pinned to 81571269ad)
Solutions
- Verify the owner/repo slug is correct and that the repository is public (or add a token-backed fetch path).
- Check https://www.githubstatus.com for an active GitHub API incident if the status starts with 5.
- Wait for the rate-limit window to reset and clear the in-memory cache via clearCache() before retrying, since successful responses are cached for 5 minutes.
- If recurring, switch to an authenticated request path so the limit is 5000/hr instead of 60/hr.
Example fix
// before
throw new GitHubApiError(`GitHub API error: ${response.status} ${response.statusText}`, response.status)
// after — distinguish retryable 5xx from permanent 4xx so callers can back off
const isRetryable = response.status >= 500
throw new GitHubApiError(
`GitHub API error: ${response.status} ${response.statusText}`,
response.status,
isRetryable
) Defensive patterns
Strategy: retry
Validate before calling
// Before scanning, sanity-check the slug and that the user has rate-limit budget.
function isValidRepoSlug(owner: string, repo: string): boolean {
return /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/.test(owner) && /^[A-Za-z0-9_.-]{1,100}$/.test(repo)
}
if (!isValidRepoSlug(owner, repo)) throw new Error('Invalid owner/repo slug') Type guard
import { GitHubApiError } from './github-fetcher'
function isGitHubApiError(e: unknown): e is GitHubApiError {
return e instanceof Error && (e as GitHubApiError).name === 'GitHubApiError' && typeof (e as GitHubApiError).statusCode === 'number'
}
function isRetryableStatus(code: number): boolean {
return code === 429 || code >= 500
} Try / catch
try {
await detectSkillsInRepo(owner, repo)
} catch (error) {
if (isGitHubApiError(error) && isRetryableStatus(error.statusCode)) {
clearCache()
await backoffRetry(() => detectSkillsInRepo(owner, repo))
} else if (isGitHubApiError(error) && error.statusCode === 404) {
showUser('Repository not found')
} else {
throw error
}
} Prevention
- Clear the githubFetch cache before retrying so stale successful responses do not mask the failure.
- Use an authenticated token for GitHub API calls to lift the 60/hr unauthenticated limit to 5000/hr.
- Validate the owner/repo slug format before issuing any API call to avoid wasted requests.
When it happens
Trigger: Calling skills:scan-repo or skills:install with an owner/repo whose API responds with 401 (repo made private), 422 (validation), 451 (DMCA-taken-down), 500/502/503 (GitHub outage), or 5xx from raw.githubusercontent. Also reached when a transient network proxy returns a non-standard status. Any githubFetch-based call (fetchRepoTree, fetchRepoContents, detectSkillsInRepo) hits this path after 404/403 are filtered out.
Common situations: Repo was renamed/deleted/made private after a skill was installed; GitHub is having an incident (5xx); corporate proxy injects a 407/502; the owner/repo pair is mistyped and not in a shape that yields 404 (e.g. empty owner triggers 404, but an org with a forbidden action yields 403/401). Unauthenticated calls during heavy skill discovery exhaust the 60/hr limit and surface as 403 first, but secondary calls during the same window can land here.
Related errors
- Failed to fetch file: ${filePath}
- GitHub API rate limit exceeded. Try again later.
- Found ${limitedPaths.length} skill(s) in ${owner}/${repo} bu
- Knowledge base name cannot be empty
- ${response.status} ${response.statusText}: ${text}
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/673759a93975e91d.
Report an issue: GitHub.