{"record":{"id":"9f38043ccd9c5b0b","repo":"mastra-ai/mastra","slug":"invalid-copilot-token-response-fields","errorCode":null,"errorMessage":"Invalid Copilot token response fields","messagePattern":"Invalid Copilot token response fields","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/auth/providers/github-copilot.ts","lineNumber":357,"sourceCode":"      headers: {\n        Accept: 'application/json',\n        Authorization: `Bearer ${refreshToken}`,\n        ...COPILOT_HEADERS,\n      },\n    },\n    signal,\n  );\n\n  if (!raw || typeof raw !== 'object') {\n    throw new Error('Invalid Copilot token response');\n  }\n\n  const obj = raw as Record<string, unknown>;\n  const token = obj.token;\n  const expiresAt = obj.expires_at;\n\n  if (typeof token !== 'string' || typeof expiresAt !== 'number') {\n    throw new Error('Invalid Copilot token response fields');\n  }\n\n  const credentials: GitHubCopilotCredentials = {\n    refresh: refreshToken,\n    access: token,\n    // expires_at is seconds; subtract 5 minutes so we refresh before actual expiry.\n    expires: expiresAt * 1000 - 5 * 60 * 1000,\n  };\n  if (enterpriseDomain) {\n    credentials.enterpriseUrl = enterpriseDomain;\n  }\n  return credentials;\n}\n\n/**\n * Login with GitHub Copilot OAuth (device-code flow).\n *\n * Prompts for an optional GitHub Enterprise URL/domain, performs the device-code flow,","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/auth/providers/github-copilot.ts#L339-L375","documentation":"After `refreshGitHubCopilotToken` confirms the Copilot token response is an object, it validates that `token` is a string and `expires_at` is a number (Unix seconds). Missing or mistyped fields mean the Copilot token endpoint returned ok but without the expected bearer-token payload, so the SDK refuses to build `GitHubCopilotCredentials`. This is defensive validation against API drift or partial responses.","triggerScenarios":"The `/copilot_internal/v2/token` endpoint returned an object missing `token` or `expires_at` (or with a string `expires_at`), e.g. an error envelope `{message:\"...\"}` served with 200, a GHES/Copilot-for-Enterprise endpoint with a different payload version, or a proxy serving cached/partial JSON.","commonSituations":"GitHub changing or versioning the internal token endpoint shape, GHE Copilot endpoints diverging from github.com, mock servers or MSW fixtures returning incomplete token objects during development/testing.","solutions":["Log the parsed response object to identify which field is missing or mistyped","Check for an error envelope (`message`, `documentation_url`) and re-authenticate via device flow if the GitHub token is no longer accepted","If using GHES/Enterprise Copilot, verify the instance's `copilot_internal` API version matches what the SDK expects","Update test fixtures/mocks to include both `token: string` and `expires_at: number` in seconds","Upgrade the SDK after any GitHub internal API change so parsing matches the new shape"],"exampleFix":"// before: mock fixture missing fields breaks refresh\nconst body = { token: 'tid=...;exp=...;proxy-ep=...' };\n// after: include the full expected payload with expires_at in seconds\nconst body = { token: 'tid=...;exp=1799999999;proxy-ep=proxy.individual.githubcopilot.com', expires_at: 1799999999 };","handlingStrategy":"type-guard","validationCode":"// Confirm the token payload carries both required fields before passing it around\nfunction hasCopilotTokenPayload(o: unknown): boolean {\n  const r = o as Record<string, unknown> | null;\n  return !!r && typeof r.token === 'string' && r.token.includes('proxy-ep=') && typeof r.expires_at === 'number' && r.expires_at > 0;\n}","typeGuard":"function isGitHubCopilotTokenPayload(v: unknown): v is { token: string; expires_at: number } {\n  if (!v || typeof v !== 'object') return false;\n  const o = v as Record<string, unknown>;\n  return typeof o.token === 'string' && o.token.length > 0 && typeof o.expires_at === 'number' && Number.isFinite(o.expires_at);\n}","tryCatchPattern":"try {\n  const creds = await provider.credentials();\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid Copilot token response fields') {\n    // Log the payload keys to see which field is missing or mistyped, then re-auth\n    console.error('Copilot token payload invalid:', e.message);\n    return startFreshDeviceFlow();\n  }\n  throw e;\n}","preventionTips":["Treat a missing token/expires_at as an auth failure and trigger re-login, not infinite retries","Fix test fixtures/mocks to include token: string and expires_at: number (Unix seconds)","Track GitHub internal API changes; upgrade the SDK when the envelope evolves","Cache and proactively refresh credentials ~5 minutes before expiry so refreshes are predictable"],"tags":["github-copilot","token-refresh","schema-validation","response-shape"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}