{"record":{"id":"ed957fa51f174e17","repo":"mastra-ai/mastra","slug":"linear-label-returned-no-access-token","errorCode":null,"errorMessage":"Linear ${label} returned no access token.","messagePattern":"Linear (.+?) returned no access token\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/integrations/linear/integration.ts","lineNumber":645,"sourceCode":"      body: new URLSearchParams({\n        ...params,\n        client_id: this.#clientId,\n        client_secret: this.#clientSecret,\n      }),\n    });\n    if (!res.ok) {\n      const err = new Error(`Linear ${label} failed (${res.status})`);\n      (err as { status?: number }).status = res.status;\n      throw err;\n    }\n    const body = (await res.json()) as {\n      access_token?: string;\n      refresh_token?: string;\n      expires_in?: number;\n      scope?: string;\n    };\n    if (!body.access_token) {\n      throw new Error(`Linear ${label} returned no access token.`);\n    }\n    return {\n      accessToken: body.access_token,\n      refreshToken: body.refresh_token ?? null,\n      expiresAt: typeof body.expires_in === 'number' ? new Date(Date.now() + body.expires_in * 1000) : null,\n      scope: body.scope ?? null,\n    };\n  }\n\n  // ── GraphQL reads/writes ─────────────────────────────────────────────────\n\n  /** Fetch the workspace (organization) the access token is scoped to. */\n  async fetchWorkspace(accessToken: string): Promise<LinearWorkspace> {\n    const data = await linearGraphql<{ organization: { name: string; urlKey: string } }>(\n      accessToken,\n      `query { organization { name urlKey } }`,\n    );\n    return { name: data.organization.name, urlKey: data.organization.urlKey };","sourceCodeStart":627,"sourceCodeEnd":663,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/integrations/linear/integration.ts#L627-L663","documentation":"`#requestTokens` POSTs to Linear's OAuth token endpoint for either a code exchange or a refresh. If the endpoint returns HTTP 200 but the JSON body has no `access_token`, it throws this error naming the grant type ('token exchange' or 'token refresh'). A 200 without an access token violates the OAuth spec and indicates Linear returned an unexpected payload.","triggerScenarios":"`exchangeOAuthCode` or `refreshAccessToken` receiving a 200 response whose body lacks `access_token` — e.g. an intermediary/proxy returning a 200 with an empty or non-OAuth JSON body, a mocked token endpoint with incomplete fixtures, or a Linear-side anomaly.","commonSituations":"Test mocks that return 200 with `{}`; gateway/proxy rewrites stripping the body; using an incorrect token URL override pointing at something that answers 200; network appliances returning cached empty responses.","solutions":["Retry the grant once — for `refresh_token` grants note Linear rotates tokens, so re-read the stored refresh token first (the integration already reloads before refreshing).","For 'token exchange', restart the OAuth flow: authorization codes are single-use, so a new code via buildAuthorizeUrl.","Log/inspect the raw 200 body to see what was actually returned.","If mocking the token endpoint, make fixtures include `access_token` (plus `refresh_token`, `expires_in`, `scope`).","Check for proxies or middlewares between your server and LINEAR_TOKEN_URL."],"exampleFix":"// test mock before\nmockTokenEndpoint = () => ({ status: 200, body: {} });\n// after\nmockTokenEndpoint = () => ({ status: 200, body: { access_token: 'ltok', refresh_token: 'rtok', expires_in: 7200, scope: 'read comments:create' } });","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isMissingAccessTokenError(err: unknown): boolean {\n  return err instanceof Error && err.message.includes('returned no access token.');\n}","tryCatchPattern":"try {\n  const tokens = await integration.exchangeOAuthCode(code, redirectUri);\n} catch (err) {\n  if (isMissingAccessTokenError(err) && err.message.includes('token exchange')) {\n    // Authorization codes are single-use: restart the OAuth flow with a fresh code.\n    restartOAuthFlow();\n    return;\n  }\n  throw err;\n}","preventionTips":["Make token-endpoint test mocks include access_token, refresh_token, expires_in, and scope.","Restart the OAuth flow rather than retrying an exchange — codes are single-use.","For refresh grants, always reload the stored refresh token before retrying (Linear rotates it).","Inspect raw 200 bodies when this fires to spot proxy interference."],"tags":["oauth","network","linear","unexpected-response"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}