yikart/AiToEarn · warning · AppException

ResponseCode.ChannelAuthSessionCompleted

ResponseCode.ChannelAuthSessionCompleted

Error message

ChannelAuthSessionCompleted

What it means

Thrown in AuthService.completeCallback when the session status is not Pending — the flow was already completed (or failed/marked). Auth sessions are single-use: once completed and an account connected, further callbacks with the same state are rejected.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts:115

  }

  async completeCallback(
    platform: AccountType,
    callbackInput: Omit<AuthCallbackInput, 'session'>,
    sessionId: string,
  ): Promise<AuthCallbackResult> {
    const session = await this.redis.getChannelAuthSession<AuthSession>(sessionId)
    if (!this.isAccountAuthSessionRecord(session)) {
      throw new AppException(ResponseCode.ChannelAuthSessionInvalid)
    }
    if (this.isSessionExpired(session)) {
      throw new AppException(ResponseCode.ChannelAuthSessionInvalid)
    }
    if (session.platform !== platform) {
      throw new AppException(ResponseCode.ChannelAuthPlatformMismatch)
    }
    if (session.status !== ChannelAuthSessionStatus.Pending) {
      throw new AppException(ResponseCode.ChannelAuthSessionCompleted)
    }

    const provider = this.registry.getAuth(platform)
    const credentialResult = await provider.exchangeCode({
      ...callbackInput,
      session,
    })

    const credentialContext = credentialResult.accessToken
      ? this.toCredentialContext(credentialResult)
      : undefined
    const profile = credentialResult.profile
      ?? (credentialContext
        ? await provider.getProfile(credentialContext)
        : undefined)
    if (!profile) {
      throw new AppException(ResponseCode.ChannelAuthPlatformUidMissing)
    }

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Treat this as benign idempotency: return the already-connected account instead of restarting the flow if appropriate for your UX.
  2. Restart the auth flow to link additional or different accounts.
  3. Guard the client against double submission (disable the button, avoid refresh on POST callbacks).
  4. If provider retries are expected, configure the platform callback to tolerate completed sessions by looking up the session's stored accountId/accounts.

Example fix

// before: refreshing the callback URL re-runs the flow
GET /api/channels/auth/callback/instagram?code=abc&state=s1  // 2nd time -> error

// after: on ChannelAuthSessionCompleted, show the existing result instead
try { await completeCallback(...) }
catch (e) {
  if (getErrorCode(e) === ResponseCode.ChannelAuthSessionCompleted) {
    showAlreadyConnectedMessage() // do not restart flow
  }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await completeCallback(platform, input, sessionId)
}
catch (e) {
  if (getErrorCode(e) === ResponseCode.ChannelAuthSessionCompleted) {
    return renderAlreadyConnectedView(sessionId) // idempotent success
  }
  throw e
}

Prevention

When it happens

Trigger: completeCallback (or the callback endpoint) is hit a second time with the same state/sessionId — e.g. the provider retries the redirect, the user double-clicks/back-button refreshes the callback URL, or the same authorization code is delivered twice.

Common situations: Browser refresh on the callback page resubmitting the OAuth code; provider-side automatic retry of the redirect; user replaying a success URL from history; duplicate webhook/callback delivery.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/80f3d7e26c1f824f. Report an issue: GitHub.