{"record":{"id":"bb3bdafea01032e8","repo":"yikart/AiToEarn","slug":"responsecode-channelauthselectedaccountunavailable","errorCode":"ResponseCode.ChannelAuthSelectedAccountUnavailable","errorMessage":"ChannelAuthSelectedAccountUnavailable","messagePattern":"ChannelAuthSelectedAccountUnavailable","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts","lineNumber":339,"sourceCode":"    selectedAccounts: SelectedAccountIdentity[]\n    groupId?: string\n    source: string\n    allowReassign?: boolean\n  }): Promise<{ accountIds: string[], accounts: ConnectedSelectableAccount[] }> {\n    if (input.selectedAccounts.length === 0) {\n      throw new AppException(ResponseCode.ChannelAuthSelectionRequired)\n    }\n\n    const keyOf = (account: SelectedAccountIdentity) => `${account.platformUid}\\u0000${account.account ?? ''}`\n    const selectedAccounts = Array.from(\n      new Map(input.selectedAccounts.map(account => [keyOf(account), account])).values(),\n    )\n    const selectableAccounts = new Map(\n      input.selectableAccounts.map(account => [keyOf(account), account]),\n    )\n    const unknownAccount = selectedAccounts.find(account => !selectableAccounts.has(keyOf(account)))\n    if (unknownAccount) {\n      throw new AppException(ResponseCode.ChannelAuthSelectedAccountUnavailable)\n    }\n\n    const groupId = await this.resolveGroupId(input.userId, input.groupId)\n    const accountIds: string[] = []\n    const accounts: ConnectedSelectableAccount[] = []\n\n    for (const selectedAccount of selectedAccounts) {\n      const selectable = selectableAccounts.get(keyOf(selectedAccount))\n      if (!selectable) {\n        throw new AppException(ResponseCode.ChannelAuthSelectedAccountUnavailable)\n      }\n\n      const platform = selectable.platform ?? input.platform\n      const account = await this.createOrUpdateAccount({\n        userId: input.userId,\n        platform,\n        platformUid: selectable.platformUid,\n        account: selectable.account,","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts#L321-L357","documentation":"saveSelectedAccountsForUser validates every account the user selected against the selectableAccounts list captured during the auth flow. If a selected account's key is not present in that list, the server rejects the save because the account was never offered/available for selection. This guards against stale or forged selections.","triggerScenarios":"Calling saveSelectedAccountsForUser with selectedAccounts containing an entry whose keyOf(...) value does not exist in input.selectableAccounts — e.g. the client submits an account id from a previous auth session, or the selectable list was rebuilt without that account.","commonSituations":"Frontend caches selected accounts across re-auth attempts; user completes auth on one device and a stale client submits old selections; the channel provider returned a different account set than expected; race where the selectable list was refreshed between render and submit.","solutions":["Refresh the selectable accounts list from the auth flow and resubmit only accounts present in it","Clear stale client-side selected-account state before retrying the save","Log the offending key and compare with selectableAccounts to find the mismatch source","Verify the client is not submitting selections from an older auth session/token"],"exampleFix":"// before\nconst selected = staleCache.accounts // may contain accounts from an old session\nawait api.saveSelectedAccounts({ ...input, selectedAccounts: selected })\n// after\nconst selectableKeys = new Set(input.selectableAccounts.map(keyOf))\nconst selected = staleCache.accounts.filter(a => selectableKeys.has(keyOf(a)))\nawait api.saveSelectedAccounts({ ...input, selectedAccounts: selected })","handlingStrategy":"validation","validationCode":"const selectableKeys = new Set(input.selectableAccounts.map(keyOf))\nconst valid = selectedAccounts.every(a => selectableKeys.has(keyOf(a)))\nif (!valid) throw new Error('Refusing to save: some selected accounts are not selectable')","typeGuard":"function isSelectable(a, list) { return list.some(s => keyOf(s) === keyOf(a)) }","tryCatchPattern":"try {\n  await api.saveSelectedAccounts(input)\n} catch (e) {\n  if (e.code === 'ChannelAuthSelectedAccountUnavailable') {\n    await refreshSelectableAccounts(); return retryWithValidSelections()\n  }\n  throw e\n}","preventionTips":["Always derive selections from the current selectableAccounts response","Clear stale selection state when restarting an auth flow","Use a stable deterministic keyOf on both client and server"],"tags":["channel-auth","validation","stale-state"],"backgroundTag":"channel-auth-account-unavailable","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}