fix(github): attribute GitHub API outages instead of blank/"failed" states

When GitHub's API is unreachable (5xx outage, network, or rate limit), Orca
showed no PR data with no explanation, so it read as an Orca bug rather than a
GitHub-side problem.

- Add a shared classifier (classifyGitHubUnavailable) reused by the main
  process and the renderer so every surface attributes an outage identically.
  A live outage returns HTTP 5xx, which the PR-refresh classifier previously
  had no branch for (fell through to the un-attributed "refresh failed").
- Right-sidebar Checks panel: show GitHub-attributed copy in the error
  empty-state, plus an inline banner over stale cached PR data so an outage
  doesn't look like a normal (silently out-of-date) panel.
- Tasks/PR-list page: replace the vague "N of M projects failed to load" with
  a GitHub-attributed banner when the failure is a reachability problem.

Copy names GitHub as the source and reassures it isn't an Orca problem, with no
status-page link. Stays GitHub-scoped so GitLab/other providers aren't
mislabeled.
This commit is contained in:
Brennan Benson
2026-07-16 17:37:04 -07:00
parent 8461df2139
commit d7d28cd7a8
16 changed files with 501 additions and 56 deletions
+11 -11
View File
@@ -29,6 +29,7 @@ import {
} from '../../shared/hosted-review-refs'
import { normalizeGitHubPRMergeMethodSettings } from '../../shared/github-pr-merge-methods'
import { isGitHubWorkItemsQueryTooLarge } from '../../shared/github-work-items-query-bounds'
import { classifyGitHubUnavailable } from '../../shared/github-api-availability'
import { parseTaskQuery, type ParsedTaskQuery } from '../../shared/task-query'
import {
GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE,
@@ -184,18 +185,15 @@ function classifyPRRefreshError(
err: unknown
): Extract<PRRefreshOutcome, { kind: 'upstream-error' }>['errorType'] {
const message = err instanceof Error ? err.message : String(err)
// Why: GitHub-unreachable buckets (5xx outage / network / rate limit) share
// one detector with the Tasks work-item path so both surfaces attribute an
// outage to GitHub identically. Rate-limit responses also carry "HTTP 403",
// so this must run before the permission check below.
const unavailable = classifyGitHubUnavailable(message)
if (unavailable) {
return unavailable
}
const lower = message.toLowerCase()
if (lower.includes('rate limit')) {
return 'rate_limited'
}
if (
lower.includes('timeout') ||
lower.includes('no such host') ||
lower.includes('network') ||
lower.includes('could not resolve host')
) {
return 'network'
}
if (lower.includes('http 403') || lower.includes('resource not accessible')) {
return 'permission'
}
@@ -215,6 +213,8 @@ function safePRRefreshErrorMessage(
return 'GitHub authentication is unavailable. Check your gh login.'
case 'network':
return 'GitHub is unreachable right now. Check your network and try again.'
case 'server_error':
return "GitHub's API is temporarily unavailable (server error). This is a GitHub-side issue."
case 'permission':
return 'GitHub did not allow access to this pull request.'
case 'repo_unavailable':