{"record":{"id":"fc33fdfe5310bc70","repo":"mastra-ai/mastra","slug":"failed-to-list-repos-res-status","errorCode":null,"errorMessage":"Failed to list repos (${res.status})","messagePattern":"Failed to list repos \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts","lineNumber":190,"sourceCode":"    throw new Error(body?.error ?? `Failed to save GitHub token (${res.status})`);\n  }\n}\n\n/** Remove an org GitHub PAT. */\nexport async function deleteGithubPat(baseUrl: string, kind: GithubPatKind = 'default'): Promise<void> {\n  const res = await fetch(`${baseUrl}/web/github/pat?kind=${kind}`, {\n    method: 'DELETE',\n    headers: { Accept: 'application/json' },\n    credentials: 'include',\n  });\n  if (!res.ok) throw new Error(`Failed to remove GitHub token (${res.status})`);\n}\n\n/** List repos across the user's installations, optionally filtered by query. */\nexport async function listGithubRepos(baseUrl: string, query?: string): Promise<GithubRepo[]> {\n  const url = query ? `${baseUrl}/web/github/repos?q=${encodeURIComponent(query)}` : `${baseUrl}/web/github/repos`;\n  const res = await fetch(url, { headers: { Accept: 'application/json' }, credentials: 'include' });\n  if (!res.ok) throw new Error(`Failed to list repos (${res.status})`);\n  const body = (await res.json()) as { repos: GithubRepo[] };\n  return body.repos;\n}\n\n/** The GitHub source-control integration id registered on the server. */\nconst GITHUB_INTEGRATION_ID = 'github';\n\n/** A Factory project row from `/web/factory/projects`. */\nexport interface FactoryProjectPayload {\n  id: string;\n  name: string;\n  /** Org-wide default model for factory runs; null when unset. */\n  defaultModelId?: string | null;\n  /** Whether new Slack sessions create Work-board items for this Factory. */\n  slackWorkItemsEnabled?: boolean;\n  /** Whether Factory rules may start agent runs without someone asking for them. */\n  autoRunEnabled?: boolean;\n}","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts#L172-L208","documentation":"listGithubRepos GETs `${baseUrl}/web/github/repos` (optionally with `?q=`) and throws this error when the response is not ok, before attempting to parse `{ repos: GithubRepo[] }`. It signals the repo listing could not be retrieved for the user's GitHub App installations.","triggerScenarios":"Non-ok response from GET /web/github/repos: 401 when no GitHub App installation is linked or the session is unauthenticated, 403 insufficient permissions/installation revoked, 429 GitHub rate limit proxied by the server, 502/504 when the server's call to the GitHub API fails or times out.","commonSituations":"GitHub App installation uninstalled or suspended; token expired server-side; searching repos while GitHub API rate limit is exhausted; server cannot reach GitHub (network egress blocked in self-hosted environments).","solutions":["Check the status: 401/403 → reinstall/re-authorize the GitHub App or re-login; 429 → back off and retry after the rate-limit window","Handle the throw in the query hook (React Query will retry automatically) and show an empty/error state instead of crashing","Verify the installation is still active for the target org on GitHub → Settings → Applications","Confirm server egress to api.github.com is allowed in self-hosted deployments","Retry with a narrower `q` if a broad query correlates with rate-limit hits"],"exampleFix":"// before\nconst repos = await listGithubRepos(baseUrl, query);\n// after\nconst repos = await listGithubRepos(baseUrl, query).catch((e) => {\n  if (/\\(429\\)/.test(e.message)) return []; // rate-limited: show empty state\n  throw e;\n});","handlingStrategy":"retry","validationCode":"function canListRepos(): boolean {\n  return typeof baseUrl === 'string' && baseUrl.length > 0; // listing needs no client-side input; ensure a query hook is mounted with an authenticated session\n}","typeGuard":"function isGithubRepoList(b: unknown): b is { repos: GithubRepo[] } {\n  return typeof b === 'object' && b !== null && Array.isArray((b as { repos?: unknown }).repos);\n}","tryCatchPattern":"useQuery({\n  queryKey: ['github','repos', query],\n  queryFn: () => listGithubRepos(baseUrl, query),\n  retry: (count, err) => /\\((429|5\\d\\d)\\)/.test(err.message) && count < 3,\n  staleTime: 60_000,\n});","preventionTips":["Use React Query with conditional retry on 429/5xx only","Cache repo lists (staleTime) to reduce GitHub rate-limit exposure","Check GitHub App installation status before offering repo pickers","Handle the error state in the UI with a retry button, not a crash","Alert on server-side GitHub API failures if self-hosting (egress/rate limits)"],"tags":["http","github","fetch","rate-limit"],"backgroundTag":"github-api-request-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}