{"record":{"id":"72e71d99f7c4604b","repo":"yikart/AiToEarn","slug":"failed-to-save-user-tokens","errorCode":null,"errorMessage":"Failed to save user tokens","messagePattern":"Failed to save user tokens","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/youtube/youtube.auth.service.ts","lineNumber":641,"sourceCode":"        {\n          $set: {\n            'googleAccount.accessToken': tokens.accessToken,\n            'googleAccount.refreshToken': tokens.refreshToken,\n            'googleAccount.expiresAt': tokens.expiresAt || (getCurrentTimestamp() + 3600)\n          }\n        }\n      );\n\n      // 缓存访问令牌\n      const expiresIn = tokens.expiresAt ? tokens.expiresAt - getCurrentTimestamp() : 3600;\n      await this.redisService.setKey(\n        `google:accessToken:${userId}`,\n        { access_token: tokens.accessToken, refresh_token: tokens.refreshToken },\n        expiresIn > 0 ? expiresIn : 3600\n      );\n    } catch (error) {\n      console.error('Error saving user tokens:', error);\n      throw new Error('Failed to save user tokens');\n    }\n  }\n}\n","sourceCodeStart":623,"sourceCodeEnd":645,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/youtube/youtube.auth.service.ts#L623-L645","documentation":"saveUserTokens persists the OAuth access/refresh tokens to the token store (keyed as google:accessToken:<userId> with TTL). Any exception thrown by the underlying storage write (connection failure, serialization issue, client error) is caught, logged, and rethrown as 'Failed to save user tokens'. The original error is in the console log output.","triggerScenarios":"Token store (e.g. Redis) is down or unreachable when the OAuth callback or token refresh tries to save tokens; expiresIn/TTL handling rejected; the storage client throws for any write of {access_token, refresh_token}.","commonSituations":"Redis not running in dev after fresh clone; Redis auth misconfigured (wrong password) so writes fail; network partition between server and token store; storing null/undefined token values from a failed Google token exchange.","solutions":["Check the server console for the logged 'Error saving user tokens:' line to see the real underlying error","Verify the token store (Redis) is running and reachable with correct credentials (redis-cli ping, check connection config)","Log the original error object in the catch before rethrowing to preserve the cause","Retry the OAuth flow; if Google returned an error token response, validate tokens before calling saveUserTokens"],"exampleFix":"// before\n} catch (error) {\n  console.error('Error saving user tokens:', error);\n  throw new Error('Failed to save user tokens');\n}\n// after\n} catch (error) {\n  console.error('Error saving user tokens:', error);\n  throw new Error(`Failed to save user tokens: ${error instanceof Error ? error.message : String(error)}`);\n}","handlingStrategy":"retry","validationCode":"const ping = await redis.ping();\nif (ping !== 'PONG') throw new Error('Token store unavailable before saving YouTube tokens');","typeGuard":"function hasTokens(t: any): t is { accessToken: string; refreshToken: string } {\n  return !!t && typeof t.accessToken === 'string' && t.accessToken.length > 0;\n}","tryCatchPattern":"try {\n  await youtubeAuthService.saveUserTokens(userId, tokens);\n} catch (e) {\n  logger.error({ err: e }, 'token save failed');\n  await withRetry(() => youtubeAuthService.saveUserTokens(userId, tokens), 3);\n}","preventionTips":["Ensure the token store (Redis/DB) is running and credentials are set in env before accepting OAuth callbacks","Add health checks for the token store and alert on failures","Validate the token exchange result (access_token present) before persisting","Log the underlying error cause, not just a generic message"],"tags":["oauth","storage","redis","token-persistence"],"backgroundTag":"token-storage-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}