{"record":{"id":"c1b607093280a562","repo":"yikart/AiToEarn","slug":"this-account-requires-relay","errorCode":null,"errorMessage":"This account requires relay","messagePattern":"This account requires relay","errorType":"exception","errorClass":"RelayAccountException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/works/work.service.ts","lineNumber":229,"sourceCode":"    const credential = await this.authService.getValidCredential(accountId, userId)\n    return {\n      accessToken: credential.accessToken,\n      refreshToken: credential.refreshToken,\n      platformUid: account.uid,\n      account: account.account,\n    }\n  }\n\n  private async getPlatformAccount(userId: string, accountId: string, platform: AccountType) {\n    const account = await this.accountRepository.getByIdAndUserId(accountId, userId)\n    if (!account) {\n      throw new AppException(ResponseCode.AccountNotFound)\n    }\n    if (account.type !== platform) {\n      throw new AppException(ResponseCode.ChannelAuthPlatformMismatch)\n    }\n    if (account.relayAccountRef) {\n      throw new RelayAccountException(account.relayAccountRef, accountId)\n    }\n    return account\n  }\n\n  private async callPlatformProvider<T>(accountId: string | undefined, action: () => Promise<T>): Promise<T> {\n    try {\n      return await action()\n    }\n    catch (error) {\n      if (accountId) {\n        await this.authService.markAccountOfflineForCredentialFailure(accountId, error, 'platform_auth_failed')\n      }\n      throw error\n    }\n  }\n\n  private async saveWorkSnapshots(\n    userId: string | undefined,","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/works/work.service.ts#L211-L247","documentation":"getPlatformAccount() rejects accounts that are configured as relay accounts. When account.relayAccountRef is set, a RelayAccountException carrying the relayAccountRef is thrown instead of returning the account, because direct platform API calls cannot be made through a non-relay-capable code path for that account.","triggerScenarios":"Calling resolveLinkInfo or account endpoints with an accountId whose stored account has relayAccountRef populated — i.e. the account is registered to operate via Relay, so direct provider access is refused.","commonSituations":"Passing a relay-managed account ID into APIs that expect a directly-connected platform account; environment mismatch where the account was created under relay mode; copying an accountId from a relay setup into a non-relay flow.","solutions":["Use an account without relayAccountRef (a directly-connected account) for these endpoints","Route the operation through the Relay flow using the relayAccountRef provided in the exception","Check the environment/key pairing: cn accounts with aitoearn.cn, intl accounts with aitoearn.ai per project rules","Re-create or re-link the account in non-relay mode if direct access is required"],"exampleFix":"// before\nconst info = await workService.resolveLinkInfo(userId, relayAccountId) // throws\n// after\nconst acct = await accountService.get(userId, id)\nif (acct.relayAccountRef) {\n  await relayClient.execute(acct.relayAccountRef, 'resolveLinkInfo', payload)\n} else {\n  const info = await workService.resolveLinkInfo(userId, acct.id)\n}","handlingStrategy":"fallback","validationCode":"const acct = await accountStore.get(accountId)\nif (acct && acct.relayAccountRef) {\n  // must go through relay\n}\n","typeGuard":"function isDirectAccount(a: { relayAccountRef?: string | null }): boolean {\n  return !a.relayAccountRef\n}","tryCatchPattern":"try {\n  const info = await workService.resolveLinkInfo(userId, accountId)\n} catch (e) {\n  if (e instanceof RelayAccountException) {\n    result = await relayClient.execute(e.relayAccountRef, 'resolveLinkInfo', payload)\n  } else throw e\n}","preventionTips":["Check relayAccountRef before choosing direct vs relay code paths","Match environment and API key (aitoearn.cn vs aitoearn.ai)","Keep relay and direct account IDs clearly separated in your data model","Handle RelayAccountException by switching transports, not by retrying the same call"],"tags":["relay","account-config","unsupported-operation"],"backgroundTag":"relay-account-required","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}