{"record":{"id":"70c7d1c56d482720","repo":"mastra-ai/mastra","slug":"failed-to-load-github-token-status-res-status","errorCode":null,"errorMessage":"Failed to load GitHub token status (${res.status})","messagePattern":"Failed to load GitHub token status \\((.+?)\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts","lineNumber":158,"sourceCode":"/** `default` = the worker token every sandbox gets; `reviewer` = optional\n * token review-board sessions use so PR reviews come from another account. */\nexport type GithubPatKind = 'default' | 'reviewer';\n\nexport interface GithubPatStatus {\n  configured: boolean;\n  reviewerConfigured: boolean;\n}\n\n/**\n * Which GitHub Personal Access Tokens the org has configured for `gh` CLI\n * use in sandboxes. The tokens themselves never reach the browser.\n */\nexport async function fetchGithubPatStatus(baseUrl: string): Promise<GithubPatStatus> {\n  const res = await fetch(`${baseUrl}/web/github/pat`, {\n    headers: { Accept: 'application/json' },\n    credentials: 'include',\n  });\n  if (!res.ok) throw new Error(`Failed to load GitHub token status (${res.status})`);\n  return (await res.json()) as GithubPatStatus;\n}\n\n/** Save an org GitHub PAT (used only for `gh` CLI auth in sandboxes). */\nexport async function saveGithubPat(baseUrl: string, token: string, kind: GithubPatKind = 'default'): Promise<void> {\n  const res = await fetch(`${baseUrl}/web/github/pat`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n    credentials: 'include',\n    body: JSON.stringify({ token, kind }),\n  });\n  if (!res.ok) {\n    const body = (await res.json().catch(() => undefined)) as { error?: string } | undefined;\n    throw new Error(body?.error ?? `Failed to save GitHub token (${res.status})`);\n  }\n}\n\n/** Remove an org GitHub PAT. */","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts#L140-L176","documentation":"fetchGithubPatStatus fetches `${baseUrl}/web/github/pat` and throws `Failed to load GitHub token status (${res.status})` when the response is not ok. It reports that the GitHub PAT status endpoint rejected the request; only the HTTP status is included, with no server message parsing.","triggerScenarios":"useGithubPatStatusQuery runs and the endpoint returns non-2xx: 401/403 when the session cookie is missing/expired or the caller lacks org access, 404 when the /web/github/pat route isn't deployed or baseUrl is wrong, or 5xx from a backend failure.","commonSituations":"Session expired while the dashboard polls token status, wrong baseUrl/proxy in dev causing 404, org without GitHub integration configured hitting an unauthorized route, or backend outage returning 502/503 through a proxy.","solutions":["Check the status in the message: 401/403 -> re-authenticate and ensure cookies are sent (credentials: 'include' needs CORS credential support).","Verify baseUrl points at the deployment that serves /web/github/pat (404 indicates a wrong base URL or missing route).","Retry with React Query's built-in retry/backoff for transient 5xx/network failures.","Inspect server logs for the backend error if the status is 500."],"exampleFix":"// before\nconst status = await fetchGithubPatStatus(baseUrl);\n// after\nconst { data, error } = useGithubPatStatusQuery(baseUrl);\nif (error instanceof Error && /\\((\\d{3})\\)/.test(error.message) && error.message.endsWith('(401)')) {\n  redirectToLogin();\n}","handlingStrategy":"retry","validationCode":"// verify baseUrl serves the endpoint before querying\nconst reachable = await fetch(`${baseUrl}/web/github/pat`, { method: 'HEAD', credentials: 'include' }).then(r => r.status !== 404).catch(() => false);\nif (!reachable) throw new Error('GitHub PAT endpoint unavailable: check baseUrl/deployment');","typeGuard":"function isGithubPatStatus(x: unknown): x is GithubPatStatus {\n  return typeof x === 'object' && x !== null && 'hasToken' in x;\n}","tryCatchPattern":"useQuery({\n  queryKey: ['github-pat-status', baseUrl],\n  queryFn: () => fetchGithubPatStatus(baseUrl),\n  retry: (count, e) => {\n    const m = /\\((\\d{3})\\)/.exec((e as Error).message);\n    const status = m ? Number(m[1]) : 0;\n    return count < 3 && (status === 0 || status >= 500);\n  },\n  onError: (e: Error) => {\n    if (e.message.endsWith('(401)')) redirectToLogin();\n  },\n});","preventionTips":["Configure React Query retries only for transient statuses (0/network, 5xx); never retry 401/403/404.","Treat 401 from the PAT status query as a session-expiry signal and trigger re-login.","Validate baseUrl per environment (dev/staging/prod) before mounting queries that hit /web/github/pat.","Keep cookies flowing for cross-origin deployments via proper CORS credentials configuration."],"tags":["http-error","github","authentication","network"],"backgroundTag":"http-request-failed-with-status","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}