yikart/AiToEarn · error · Error
Failed to save user tokens
Error message
Failed to save user tokens
What it means
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.
Source
Thrown at project/aitoearn-electron/server/src/modules/plat/youtube/youtube.auth.service.ts:641
{
$set: {
'googleAccount.accessToken': tokens.accessToken,
'googleAccount.refreshToken': tokens.refreshToken,
'googleAccount.expiresAt': tokens.expiresAt || (getCurrentTimestamp() + 3600)
}
}
);
// 缓存访问令牌
const expiresIn = tokens.expiresAt ? tokens.expiresAt - getCurrentTimestamp() : 3600;
await this.redisService.setKey(
`google:accessToken:${userId}`,
{ access_token: tokens.accessToken, refresh_token: tokens.refreshToken },
expiresIn > 0 ? expiresIn : 3600
);
} catch (error) {
console.error('Error saving user tokens:', error);
throw new Error('Failed to save user tokens');
}
}
}
View on GitHub (pinned to d3aa8bea5b)
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
Example fix
// before
} catch (error) {
console.error('Error saving user tokens:', error);
throw new Error('Failed to save user tokens');
}
// after
} catch (error) {
console.error('Error saving user tokens:', error);
throw new Error(`Failed to save user tokens: ${error instanceof Error ? error.message : String(error)}`);
} Defensive patterns
Strategy: retry
Validate before calling
const ping = await redis.ping();
if (ping !== 'PONG') throw new Error('Token store unavailable before saving YouTube tokens'); Type guard
function hasTokens(t: any): t is { accessToken: string; refreshToken: string } {
return !!t && typeof t.accessToken === 'string' && t.accessToken.length > 0;
} Try / catch
try {
await youtubeAuthService.saveUserTokens(userId, tokens);
} catch (e) {
logger.error({ err: e }, 'token save failed');
await withRetry(() => youtubeAuthService.saveUserTokens(userId, tokens), 3);
} Prevention
- 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
When it happens
Trigger: 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}.
Common situations: 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.
Related errors
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/72e71d99f7c4604b.
Report an issue: GitHub.