{"record":{"id":"4921d0b2d55d6d0b","repo":"Foundry376/Mailspring","slug":"oauth-code-exchange-returned-resp-status-resp","errorCode":null,"errorMessage":"OAuth Code exchange returned ${resp.status} ${resp.statusText}: ${JSON.stringify(json)}","messagePattern":"OAuth Code exchange returned (.+?) (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/internal_packages/onboarding/lib/onboarding-helpers.ts","lineNumber":63,"sourceCode":"  };\n\n  const idString = `${emailAddress}${JSON.stringify(settingsThatCouldChangeMailContents)}`;\n  return crypto.createHash('sha256').update(idString, 'utf8').digest('hex').substr(0, 8);\n}\n\nasync function fetchPostWithFormBody<T>(url: string, body: { [key: string]: string }) {\n  const resp = await fetch(url, {\n    method: 'POST',\n    body: Object.entries(body)\n      .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))\n      .join('&'),\n    headers: {\n      'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n    },\n  });\n  const json = ((await resp.json()) || {}) as T;\n  if (!resp.ok) {\n    throw new Error(\n      `OAuth Code exchange returned ${resp.status} ${resp.statusText}: ${JSON.stringify(json)}`\n    );\n  }\n  return json;\n}\n\nfunction mxRecordsForDomain(domain) {\n  return new Promise<string[]>((resolve, reject) => {\n    // timeout here is annoyingly long - 30s?\n    dns.resolveMx(domain, (err, addresses) => {\n      if (err) {\n        resolve([]);\n      } else {\n        resolve(addresses.map((a) => a.exchange.toLowerCase()));\n      }\n    });\n  });\n}","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/Foundry376/Mailspring/blob/648c685d602ece6bb00c22534b8734de6ac644b3/app/internal_packages/onboarding/lib/onboarding-helpers.ts#L45-L81","documentation":"Helper guard in fetchPostWithFormBody during onboarding: the OAuth token endpoint (Google or Microsoft) returned a non-2xx status to the application/x-www-form-urlencoded POST. The body JSON is embedded so the failure reason (invalid_grant, bad client_secret, expired code) is visible; commonly caused by reusing an already-consumed authorization code or a clock-skewed/redirect_uri mismatch.","triggerScenarios":"Thrown at app/internal_packages/onboarding/lib/onboarding-helpers.ts:63 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Restart the OAuth flow to obtain a fresh authorization code — codes are single-use and short-lived","Verify redirect_uri and client credentials exactly match the provider app configuration","Inspect the embedded JSON body for the OAuth error field (e.g. invalid_grant) to pinpoint the cause"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"648c685d602ece6bb00c22534b8734de6ac644b3","analyzedAt":"2026-09-03T02:00:24.311Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}