mastra-ai/mastra · error

Invalid Copilot token response fields

Error message

Invalid Copilot token response fields

What it means

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.

Solutions

  1. Log the parsed response object to identify which field is missing or mistyped
  2. Check for an error envelope (`message`, `documentation_url`) and re-authenticate via device flow if the GitHub token is no longer accepted
  3. If using GHES/Enterprise Copilot, verify the instance's `copilot_internal` API version matches what the SDK expects
  4. Update test fixtures/mocks to include both `token: string` and `expires_at: number` in seconds
  5. Upgrade the SDK after any GitHub internal API change so parsing matches the new shape

Example fix

// before: mock fixture missing fields breaks refresh
const body = { token: 'tid=...;exp=...;proxy-ep=...' };
// after: include the full expected payload with expires_at in seconds
const body = { token: 'tid=...;exp=1799999999;proxy-ep=proxy.individual.githubcopilot.com', expires_at: 1799999999 };
Defensive patterns

Strategy: type-guard

Validate before calling

// Confirm the token payload carries both required fields before passing it around
function hasCopilotTokenPayload(o: unknown): boolean {
  const r = o as Record<string, unknown> | null;
  return !!r && typeof r.token === 'string' && r.token.includes('proxy-ep=') && typeof r.expires_at === 'number' && r.expires_at > 0;
}

Type guard

function isGitHubCopilotTokenPayload(v: unknown): v is { token: string; expires_at: number } {
  if (!v || typeof v !== 'object') return false;
  const o = v as Record<string, unknown>;
  return typeof o.token === 'string' && o.token.length > 0 && typeof o.expires_at === 'number' && Number.isFinite(o.expires_at);
}

Try / catch

try {
  const creds = await provider.credentials();
} catch (e) {
  if (e instanceof Error && e.message === 'Invalid Copilot token response fields') {
    // Log the payload keys to see which field is missing or mistyped, then re-auth
    console.error('Copilot token payload invalid:', e.message);
    return startFreshDeviceFlow();
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/9f38043ccd9c5b0b. Report an issue: GitHub.

Appendix: source

Thrown at mastracode/sdk/src/auth/providers/github-copilot.ts:357

      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${refreshToken}`,
        ...COPILOT_HEADERS,
      },
    },
    signal,
  );

  if (!raw || typeof raw !== 'object') {
    throw new Error('Invalid Copilot token response');
  }

  const obj = raw as Record<string, unknown>;
  const token = obj.token;
  const expiresAt = obj.expires_at;

  if (typeof token !== 'string' || typeof expiresAt !== 'number') {
    throw new Error('Invalid Copilot token response fields');
  }

  const credentials: GitHubCopilotCredentials = {
    refresh: refreshToken,
    access: token,
    // expires_at is seconds; subtract 5 minutes so we refresh before actual expiry.
    expires: expiresAt * 1000 - 5 * 60 * 1000,
  };
  if (enterpriseDomain) {
    credentials.enterpriseUrl = enterpriseDomain;
  }
  return credentials;
}

/**
 * Login with GitHub Copilot OAuth (device-code flow).
 *
 * Prompts for an optional GitHub Enterprise URL/domain, performs the device-code flow,

View on GitHub (pinned to 75dd419e61)