{"record":{"id":"1777a6464c66e823","repo":"mastra-ai/mastra","slug":"failed-to-remove-github-token-res-status","errorCode":null,"errorMessage":"Failed to remove GitHub token (${res.status})","messagePattern":"Failed to remove GitHub token \\((.+?)\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts","lineNumber":183,"sourceCode":"    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. */\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;","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts#L165-L201","documentation":"deleteGithubPat sends DELETE to `${baseUrl}/web/github/pat?kind=...` and throws this error on any non-ok response. It means the server refused or failed to remove the stored GitHub PAT for that kind.","triggerScenarios":"Non-ok response from DELETE /web/github/pat: 401 unauthenticated session, 403 forbidden (PAT belongs to another user/org scope), 404 unknown kind or no stored PAT, 5xx server error during removal or GitHub revocation.","commonSituations":"Deleting a PAT after the session expired; calling delete twice and the second call hits 404 (no idempotent handling here, unlike unlinkRepository); wrong `kind` value passed (e.g. 'default' vs a custom kind the server doesn't recognize).","solutions":["Check the status: 401/403 → re-authenticate or confirm the user owns the PAT; 404 → treat as already deleted and refresh local state","Retry once after session refresh; transient 5xx on revocation is common","Ensure the `kind` argument matches one registered on the server (default is 'default')","Verify baseUrl/origin so the session cookie is included via credentials: 'include'"],"exampleFix":"// before\nawait deleteGithubPat(baseUrl); // assume 404 is fine\n// after\ntry {\n  await deleteGithubPat(baseUrl, kind);\n} catch (e) {\n  if (!/\\(404\\)/.test(e.message)) throw e; // tolerate already-removed\n  queryClient.invalidateQueries(githubPatKeys.all);\n}","handlingStrategy":"try-catch","validationCode":"function canAttemptPatDelete(kind: unknown): boolean {\n  return typeof kind === 'string' && kind.length > 0;\n}","typeGuard":"const isAlreadyRemoved = (e: unknown): boolean => e instanceof Error && /\\(404\\)/.test(e.message);","tryCatchPattern":"try {\n  await deleteGithubPat(baseUrl, kind);\n} catch (e) {\n  if (isAlreadyRemoved(e)) {\n    queryClient.invalidateQueries(githubPatKeys.all); // idempotent success\n  } else if (/\\((401|403)\\)/.test(e.message)) {\n    promptReauth();\n  } else {\n    throw e;\n  }\n}","preventionTips":["Treat 404 as success for deletes to keep UI idempotent (mirrors unlinkRepository/deleteFactoryProject)","Invalidate the PAT cache after delete instead of trusting the local flag","Re-check session before destructive mutations","Pass the exact `kind` used at save time; persist it with the token record","Debounce the delete button to avoid duplicate concurrent DELETEs"],"tags":["http","github","auth","fetch"],"backgroundTag":"github-api-request-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}