{"record":{"id":"231ef2ab3215261e","repo":"musistudio/claude-code-router","slug":"grok-cli-oauth-token-refresh-did-not-return-an-acc","errorCode":null,"errorMessage":"Grok CLI OAuth token refresh did not return an access token.","messagePattern":"Grok CLI OAuth token refresh did not return an access token\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/agents/local-providers/grok.ts","lineNumber":711,"sourceCode":"      }).toString(),\n      headers: {\n        \"content-type\": \"application/x-www-form-urlencoded\"\n      },\n      method: \"POST\",\n      signal: controller.signal\n    });\n    const text = await response.text();\n    const payload = parseJsonRecord(text);\n    if (!response.ok) {\n      const message = `Grok CLI OAuth token refresh returned HTTP ${response.status}${tokenRefreshErrorMessage(payload, text)}`;\n      if (response.status === 401 || response.status === 403) {\n        throw new GrokRefreshAuthError(response.status, message);\n      }\n      throw new Error(message);\n    }\n    const accessToken = readString(payload?.access_token) || readString(payload?.accessToken);\n    if (!accessToken) {\n      throw new Error(\"Grok CLI OAuth token refresh did not return an access token.\");\n    }\n    const refreshed: GrokTokenSet = {\n      ...auth,\n      accessToken,\n      expiresAt: refreshedGrokExpiresAt(accessToken, payload),\n      refreshToken: readString(payload?.refresh_token) || readString(payload?.refreshToken) || refreshToken\n    };\n    persistRefreshedGrokAuth(refreshed);\n    return refreshed;\n  } catch (error) {\n    if (error instanceof Error && error.name === \"AbortError\") {\n      throw new Error(`Grok CLI OAuth token refresh timed out after ${timeoutMs}ms.`);\n    }\n    throw error;\n  } finally {\n    clearTimeout(timer);\n  }\n}","sourceCodeStart":693,"sourceCodeEnd":729,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/agents/local-providers/grok.ts#L693-L729","documentation":"The Grok OAuth token endpoint responded with HTTP 2xx, but the JSON body contained neither access_token nor accessToken as a non-empty string. The library treats this as a malformed token response because it cannot construct a usable GrokTokenSet without an access token.","triggerScenarios":"resolveGrokAuth triggers refreshGrokAuth; the response is ok but the payload is empty, an HTML error page parsed as an empty record, or a schema change where the token field is renamed or nested differently than access_token/accessToken.","commonSituations":"A proxy or captive portal returns 200 with an HTML body; the provider A/B tests a new response schema; the discovery document pointed at the wrong endpoint that returns 200 with unrelated JSON.","solutions":["Log/dump the raw response body to see what the endpoint actually returned","Re-run login to reset the stored credentials and token endpoint","If a proxy intercepts HTTPS, bypass it for the OAuth host","Report/check for a schema change in the Grok CLI OAuth response"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const res = await fetch(tokenEndpoint, init);\nconst body = await res.json();\nif (!res.ok || !(typeof body.access_token === 'string' && body.access_token)) {\n  // treat as malformed; re-login rather than retry","typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message.includes('did not return an access token')) {\n    await promptRelogin(); // schema/response issue, retrying won't help\n  }\n}","preventionTips":["Log the raw token response body when debugging OAuth integrations","Pin the provider CLI version that matches the tested OAuth schema"],"tags":["oauth","grok","malformed-response","token-refresh"],"backgroundTag":"oauth-malformed-token-response","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}