RocketChat/Rocket.Chat · error · Meteor.Error
error-challenge-not-found
error-challenge-not-found
Error message
challenge not found
What it means
Thrown by POST twoFactorChallenges.sendEmailCode when TwoFactorChallenges.findOneByPendingChallengeId returns nothing — no pending challenge document exists for that id. Challenges are short-lived, single-flow records created during an OAuth login that requires email 2FA; once resolved, expired, or removed they no longer match.
Source
Thrown at apps/meteor/server/api/v1/twoFactorChallenges.ts:26
import { emailCheckForOAuth, getTwoFAMethodForOAuth } from '../../lib/oauth/twoFactorAuth';
import { generateConnection } from '../ApiClass';
import { API } from '../api';
API.v1.addRoute(
'twoFactorChallenges.sendEmailCode',
{ validateParams: isTwoFactorChallengesSendEmailCodeParamsPOST, rateLimiterOptions: { intervalTimeInMS: 60000, numRequestsAllowed: 5 } },
{
async post() {
const { challengeId } = this.bodyParams;
if (!challengeId) {
throw new Meteor.Error('error-parameter-required', 'challengeId is required');
}
const challenge = await TwoFactorChallenges.findOneByPendingChallengeId(challengeId);
if (!challenge) {
throw new Meteor.Error('error-challenge-not-found', 'challenge not found');
}
if (challenge.expireAt && challenge.expireAt < new Date()) {
throw new Meteor.Error('error-challenge-expired', 'challenge expired');
}
if (challenge.method !== 'email') {
throw new Meteor.Error('error-invalid-challenge-method', 'invalid challenge method');
}
const { userId } = challenge;
const user = await getUserForCheck(userId);
if (!user) {
throw new Meteor.Error('error-user-not-found', 'user not found');
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Restart the OAuth login flow to create a fresh challenge, then resend the code
- Handle this error as 'restart flow', not 'show generic failure' — no retry of the same call can succeed
- Make sure only one component drives the challenge; parallel resends/verifies consume it
- Keep the challenge-to-page lifetime short in the UI
Example fix
// before
await retry(() => sdk.post('twoFactorChallenges.sendEmailCode', { challengeId })); // hopeless once deleted
// after
try {
await sdk.post('twoFactorChallenges.sendEmailCode', { challengeId });
} catch (e) {
if (e.error === 'error-challenge-not-found') window.location = oauthLoginUrl; // restart flow
} Defensive patterns
Strategy: try-catch
Try / catch
try { await sdk.post('twoFactorChallenges.sendEmailCode', { challengeId }); } catch (e) { if (e?.error === 'error-challenge-not-found') window.location = oauthLoginUrl; else throw e; } Prevention
- Map error-challenge-not-found to 'restart login flow' in every 2FA client
- Never auto-retry the same challengeId after failure
- Drive the challenge from a single component to avoid concurrent consumers
When it happens
Trigger: POST with a challengeId from a login attempt that already completed; a challenge removed after too many failed verify attempts (the verify endpoint deletes it); a mistyped or truncated id; calling sendEmailCode long after the initial login started.
Common situations: User leaves the 2FA page open, pastes the code much later, clicks resend, and the challenge is gone; retrying an old flow from a deep-linked page; double-submit racing where one branch finishes the challenge first.
Related errors
- error-parameter-required
- error-challenge-expired
- error-invalid-challenge-method
- error-user-not-found
- totp-max-attempts
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/6a22f5c391cc73c5.
Report an issue: GitHub.