{"record":{"id":"dbd27cbaf9844ac0","repo":"antiwork/gumroad","slug":"something-went-wrong-dbd27c","errorCode":null,"errorMessage":"Something went wrong.","messagePattern":"Something went wrong\\.","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"app/javascript/data/google_calendar_integration.ts","lineNumber":23,"sourceCode":"\ntype AccountInfo = { accessToken: string; refreshToken: string; email: string };\ntype Calendar = { id: string; summary: string };\nexport const fetchAccountInfo = async (code: string): Promise<AccountInfo> => {\n  const response = await request({\n    method: \"GET\",\n    url: Routes.account_info_integrations_google_calendar_index_path({ format: \"json\", code }),\n    accept: \"json\",\n  });\n  const responseData = typia.assert<\n    | {\n        success: true;\n        access_token: string;\n        refresh_token: string;\n        email: string;\n      }\n    | { success: false }\n  >(await response.json());\n  if (!responseData.success) throw new ResponseError();\n  return {\n    accessToken: responseData.access_token,\n    refreshToken: responseData.refresh_token,\n    email: responseData.email,\n  };\n};\n\nexport const fetchCalendarList = async (accessToken: string, refreshToken: string): Promise<Calendar[]> => {\n  const response = await request({\n    method: \"GET\",\n    url: Routes.calendar_list_integrations_google_calendar_index_path({\n      format: \"json\",\n      access_token: accessToken,\n      refresh_token: refreshToken,\n    }),\n    accept: \"json\",\n  });\n  const responseData = typia.assert<","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/data/google_calendar_integration.ts#L5-L41","documentation":"Thrown after the Google Calendar OAuth handshake: the callback page has a ?code= param and this GET to account_info_integrations_google_calendar_index_path({ code }) asks the server to exchange that code for access/refresh tokens. typia.assert narrows the reply to a success:true token set or { success: false }; on success:false a bare ResponseError('Something went wrong.') is thrown. The endpoint returns no reason, so diagnosis has to happen server-side.","triggerScenarios":"Authorization code invalid, expired (roughly 10-minute lifetime), or already redeemed (a page refresh re-submits the same code); redirect_uri mismatch between the Google OAuth client config and the request; wrong or missing client ID/secret env vars on the server; the user denied consent.","commonSituations":"A developer reloads the callback URL during testing and the one-time code is consumed twice; OAuth client credentials differ between staging and production; a Google-side incident rejects the token exchange.","solutions":["Do not re-submit a used code — restart the flow with getOAuthUrl() to mint a fresh authorization code.","Check the server logs for the Google token-exchange response; the client gets no reason in the JSON.","Verify the Google OAuth client's redirect URIs and client ID/secret for this environment.","Guard the callback route so a repeat visit (refresh/back button) re-initiates OAuth instead of replaying the code."],"exampleFix":"// before\nif (!responseData.success) throw new ResponseError();\n\n// after — at minimum log the failure server-side so the client message is not the only clue\nif (!responseData.success) throw new ResponseError(\"We couldn't connect Google Calendar. Please try connecting again.\");","handlingStrategy":"fallback","validationCode":null,"typeGuard":"const isExchangeFailure = (json: unknown): json is { success: false } =>\n  typeof json === 'object' && json !== null && (json as { success?: unknown }).success === false;","tryCatchPattern":"try {\n  const tokens = await getAccessToken(code);\n} catch (e) {\n  assertResponseError(e);\n  renderReconnectUI(); // fallback: offer a fresh OAuth round-trip, since the code is single-use\n}","preventionTips":["Never replay an OAuth code — one exchange per code, ever.","Handle the callback so refresh/back navigation restarts the flow instead of resubmitting.","Keep redirect URIs and client credentials identical across environments.","Preserve server-side logs of the Google token-exchange response; the client payload has no reason."],"tags":["oauth","google-calendar","integration","typia","response-error"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}