{"record":{"id":"9209dffe3dc391b2","repo":"yikart/AiToEarn","slug":"channelauthrefreshtokenmissing-9209df","errorCode":"ChannelAuthRefreshTokenMissing","errorMessage":"ChannelAuthRefreshTokenMissing","messagePattern":"ChannelAuthRefreshTokenMissing","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/linkedin/linkedin-auth.provider.ts","lineNumber":46,"sourceCode":"\n    return { url, state: input.state, redirectUri: this.config.redirectUri }\n  }\n\n  async exchangeCode(input: AuthCallbackInput): Promise<CredentialResult> {\n    const callback = parseOAuthCallback(input)\n    const result = await this.linkedinService.exchangeCode(callback.code)\n\n    return {\n      accessToken: result.accessToken,\n      refreshToken: result.refreshToken,\n      expiresAt: result.expiresAt,\n      scope: result.scope,\n    }\n  }\n\n  async refresh(input: RefreshCredentialInput): Promise<CredentialResult> {\n    if (!input.refreshToken) {\n      throw new AppException(ResponseCode.ChannelAuthRefreshTokenMissing)\n    }\n\n    const result = await this.linkedinService.refreshAccessToken(input.refreshToken)\n\n    return {\n      accessToken: result.accessToken,\n      refreshToken: result.refreshToken,\n      expiresAt: result.expiresAt,\n      scope: result.scope,\n    }\n  }\n\n  async revoke(input: RevokeCredentialInput): Promise<void> {\n    await this.linkedinService.revokeToken(input.accessToken)\n  }\n\n  async getProfile(input: CredentialContext): Promise<PlatformAccountProfile> {\n    const profile = await this.linkedinService.getProfile(input.accessToken)","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/linkedin/linkedin-auth.provider.ts#L28-L64","documentation":"ChannelAuthRefreshTokenMissing is thrown by the LinkedIn auth provider's refresh() when the credential being refreshed has no refreshToken. OAuth refresh requires a refresh token; without one the provider refuses to call LinkedIn's token endpoint. This is a guard against an unrecoverable credential state.","triggerScenarios":"Calling refresh() with input.refreshToken undefined/empty — typically a credential created via a flow that returned only an accessToken, or a stored credential where the refresh token was never persisted or was cleared.","commonSituations":"LinkedIn OAuth apps where the user reconnected without offline_access scope so no refresh token was issued; DB rows seeded manually with only an access token; refresh tokens deleted after revocation by the user.","solutions":["Ensure the LinkedIn OAuth authorization flow requests offline_access and stores the returned refresh token","Re-authorize the channel (re-run the connect/OAuth flow) to obtain a fresh refresh token","Check the credential storage layer for rows missing refresh_token and backfill via re-auth"],"exampleFix":"// before\nawait linkedinAuthProvider.refresh({ accessToken }) // throws ChannelAuthRefreshTokenMissing\n// after\nif (!credential.refreshToken) await reconnectChannel(credential.channelId)\nawait linkedinAuthProvider.refresh({ accessToken, refreshToken: credential.refreshToken })","handlingStrategy":"validation","validationCode":"if (!credential.refreshToken) {\n  throw new Error('LinkedIn credential missing refresh token; reconnect the channel')\n}\nawait linkedinAuthProvider.refresh({ accessToken: credential.accessToken, refreshToken: credential.refreshToken })","typeGuard":"function hasRefreshToken(c: { refreshToken?: string | null }): c is { refreshToken: string } {\n  return typeof c.refreshToken === 'string' && c.refreshToken.length > 0\n}","tryCatchPattern":"try {\n  await linkedinAuthProvider.refresh(input)\n} catch (e) {\n  if (e instanceof AppException && e.code === ResponseCode.ChannelAuthRefreshTokenMissing) {\n    await markChannelNeedsReauth(channelId)\n  } else throw e\n}","preventionTips":["Always request offline_access scope in the LinkedIn OAuth flow","Persist refresh_token immediately at connect time; never store access-token-only rows","Monitor for credentials with null refresh_token and trigger re-auth proactively","Re-auth channels after user revocation instead of calling refresh"],"tags":["linkedin","oauth","auth","missing-credential"],"backgroundTag":"missing-refresh-token","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}