{"record":{"id":"f7ae174001bff07a","repo":"yikart/AiToEarn","slug":"responsecode-channelaccesstokenfailed-f7ae17","errorCode":"ResponseCode.ChannelAccessTokenFailed","errorMessage":"ChannelAccessTokenFailed","messagePattern":"ChannelAccessTokenFailed","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"critical","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts","lineNumber":281,"sourceCode":"    source: string\n    allowReassign?: boolean\n  }): Promise<ConnectedSelectableAccount> {\n    const groupId = await this.resolveGroupId(input.userId, input.groupId)\n    const account = await this.createOrUpdateAccount({\n      userId: input.userId,\n      platform: input.platform,\n      platformUid: input.profile.platformUid,\n      account: input.profile.account,\n      displayName: input.profile.displayName,\n      avatarUrl: input.profile.avatarUrl,\n      fansCount: input.profile.fansCount,\n      followingCount: input.profile.followingCount,\n      groupId,\n      allowReassign: input.allowReassign ?? this.shouldReassignDouyinAccount(input.source, input.platform, input.credential),\n    })\n\n    if (!input.credential.accessToken) {\n      throw new AppException(ResponseCode.ChannelAccessTokenFailed)\n    }\n\n    await this.credentialService.saveCredential(account.id, input.platform, {\n      accessToken: input.credential.accessToken,\n      refreshToken: input.credential.refreshToken,\n      expiresAt: input.credential.expiresAt,\n      scope: input.credential.scope,\n      raw: input.credential.raw,\n    })\n\n    return {\n      accountId: account.id,\n      platform: input.platform,\n      platformUid: input.profile.platformUid,\n      account: input.profile.account,\n      displayName: input.profile.displayName,\n      avatarUrl: input.profile.avatarUrl,\n    }","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts#L263-L299","documentation":"Thrown in AuthService.saveAccountProfile when the CredentialResult for the connected account has no accessToken. The account record may already have been created/updated, but persisting channel credentials requires a non-empty access token, so the flow aborts.","triggerScenarios":"saveAccountProfile (via connectedAccount / completeCallback) receives a credential whose accessToken is empty/undefined — typically a provider adapter that returns a CredentialResult with refreshToken or raw data but no access token after exchangeCode, or a token exchange that silently failed and returned empty fields.","commonSituations":"Provider token endpoint returning 200 with an error body the adapter maps to an empty token; OAuth scope/app-config problems so the provider issues no token; adapter bug storing the token under a different field name; token exchange skipped for deferred-exchange platforms.","solutions":["Inspect the provider adapter's exchangeCode: confirm the access token field is mapped into CredentialResult.accessToken correctly.","Call the provider's token endpoint manually with the code to see whether a token is actually issued (check app secret, redirect_uri, grant_type).","Verify the platform app configuration (client id/secret, redirect URI) — misconfiguration often yields no token.","If the platform defers token exchange, implement the exchange before saveAccountProfile rather than passing an empty credential."],"exampleFix":"// before: adapter drops the token field\nreturn { refreshToken: res.refresh_token, expiresAt } // accessToken missing\n\n// after: map the access token explicitly\nreturn { accessToken: res.access_token, refreshToken: res.refresh_token, expiresAt }","handlingStrategy":"validation","validationCode":"function credentialHasAccessToken(c: CredentialResult): boolean {\n  return typeof c.accessToken === 'string' && c.accessToken.length > 0\n}\nif (!credentialHasAccessToken(credentialResult)) {\n  throw new Error('Token exchange produced no access token — inspect provider token endpoint response')\n}","typeGuard":"function hasAccessToken(c: CredentialResult): c is CredentialResult & { accessToken: string } {\n  return typeof c.accessToken === 'string' && c.accessToken.length > 0\n}","tryCatchPattern":"try {\n  await connectAccountProfile(input)\n}\ncatch (e) {\n  if (getErrorCode(e) === ResponseCode.ChannelAccessTokenFailed) {\n    logCredentialExchangeDebug(input.platform) // no accessToken from exchangeCode\n  }\n}","preventionTips":["Schema-validate provider token responses (access_token must be a non-empty string)","Verify client secret/redirect_uri/grant_type per platform app config","Add adapter unit tests asserting accessToken mapping","Alert on empty access_token fields in provider responses"],"tags":["oauth","access-token","credential","adapter"],"backgroundTag":"oauth-access-token-missing","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}