{"record":{"id":"ea7c9da2d5aa0b45","repo":"decolua/9router","slug":"gitlab-token-exchange-failed-await-response-te","errorCode":null,"errorMessage":"`GitLab token exchange failed: ${await response.text()}`","messagePattern":"`GitLab token exchange failed: (.+?)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/providers/gitlab.js","lineNumber":39,"sourceCode":"  },\n  exchangeToken: async (config, code, redirectUri, codeVerifier, state, meta = {}) => {\n    const baseUrl = meta.baseUrl || config.defaultBaseUrl;\n    const clientId = meta.clientId || \"\";\n    const clientSecret = meta.clientSecret || \"\";\n    const body = new URLSearchParams({\n      client_id: clientId,\n      grant_type: \"authorization_code\",\n      code,\n      redirect_uri: redirectUri,\n      code_verifier: codeVerifier,\n    });\n    if (clientSecret) body.set(\"client_secret\", clientSecret);\n    const response = await fetch(`${baseUrl}${config.tokenUrlPath}`, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/x-www-form-urlencoded\", Accept: \"application/json\" },\n      body: body.toString(),\n    });\n    if (!response.ok) throw new Error(`GitLab token exchange failed: ${await response.text()}`);\n    const tokens = await response.json();\n    // Fetch user info\n    const userRes = await fetch(`${baseUrl}${config.userInfoUrlPath}`, {\n      headers: { Authorization: `Bearer ${tokens.access_token}` },\n    });\n    const user = userRes.ok ? await userRes.json() : {};\n    return { ...tokens, _user: user, _baseUrl: baseUrl, _clientId: clientId };\n  },\n  mapTokens: (tokens) => ({\n    accessToken: tokens.access_token,\n    refreshToken: tokens.refresh_token,\n    expiresIn: tokens.expires_in,\n    scope: tokens.scope,\n    providerSpecificData: {\n      username: tokens._user?.username || \"\",\n      email: tokens._user?.email || tokens._user?.public_email || \"\",\n      name: tokens._user?.name || \"\",\n      baseUrl: tokens._baseUrl,","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/providers/gitlab.js#L21-L57","documentation":"GitLab OAuth token exchange: the POST (application/x-www-form-urlencoded) to <baseUrl><tokenUrlPath> returned a non-2xx status and the body text is thrown. This provider is instance-relative, so baseUrl mistakes are a leading cause. No tokens are returned and the subsequent userInfo fetch never runs.","triggerScenarios":"Calling exchangeCode for gitlab when the token endpoint replies !response.ok — 401 invalid_client (bad/missing client_secret), invalid_grant (code expired/replayed), redirect_uri mismatch, wrong baseUrl (self-hosted instance path or /api/v4 confusion), or 5xx.","commonSituations":"Self-hosted GitLab with wrong baseUrl or token path; client_secret omitted even though the instance requires it; code replayed after callback retry; GitLab.com outage; reverse proxy stripping the POST body.","solutions":["Inspect the body — GitLab returns {error: \"invalid_grant\"|\"invalid_client\", error_description} which names the cause.","Confirm baseUrl points at the instance root and tokenUrlPath resolves to /oauth/token.","Ensure client_secret is set when the GitLab app is configured as confidential.","Restart the flow for a fresh code and verify redirect_uri matches the app registration exactly."],"exampleFix":"// before\nif (!response.ok) throw new Error(`GitLab token exchange failed: ${await response.text()}`);\n// after\nif (!response.ok) {\n  const t = await response.text();\n  throw new Error(`GitLab token exchange failed (HTTP ${response.status} at ${baseUrl}${config.tokenUrlPath}): ${t}`);\n}","handlingStrategy":"try-catch","validationCode":"// Validate GitLab target before exchanging\nconst u = new URL(baseUrl + config.tokenUrlPath);\nif (!u.pathname.endsWith(\"/oauth/token\")) throw new Error(`Suspicious GitLab token path: ${u.pathname}`);\nif (clientSecretRequired && !clientSecret) throw new Error(\"Confidential GitLab app requires client_secret\");","typeGuard":"function isGitlabTokenError(body) {\n  return typeof body === \"object\" && body !== null &&\n    typeof body.error === \"string\"; // \"invalid_grant\" | \"invalid_client\"\n}","tryCatchPattern":"try {\n  const session = await gitlabProvider.exchangeCode(code);\n} catch (err) {\n  if (String(err.message).includes(\"GitLab token exchange failed\")) {\n    if (/invalid_client/.test(err.message)) console.error(\"Check GitLab client_id/client_secret\");\n    else if (/invalid_grant/.test(err.message)) await restartGitlabLogin();\n    else console.error(\"GitLab token endpoint error:\", err.message);\n  } else throw err;\n}","preventionTips":["Double-check baseUrl for self-hosted instances (instance root, correct token path).","Set client_secret whenever the GitLab application is confidential.","Restart the flow instead of retrying an already-consumed authorization code.","Log the resolved token URL so misconfigurations are immediately visible."],"tags":["oauth","gitlab","token-exchange","http-error"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}