eyaltoledano/claude-task-master · error · AuthenticationError

MFA_VERIFICATION_FAILED

MFA_VERIFICATION_FAILED

Error message

Failed to create MFA challenge: ${challengeError?.message || 'Unknown error'}

What it means

SupabaseClient.verifyMFA() throws this when `client.auth.mfa.challenge({factorId})` returns an error or no challenge data. Before a TOTP code can be verified, a challenge must be created for the factor; if that fails, verification cannot proceed. Code: MFA_VERIFICATION_FAILED.

Source

Thrown at packages/tm-core/src/modules/integration/clients/supabase-client.ts:552

			return { required: false };
		}
	}

	/**
	 * Verify MFA code and upgrade session to AAL2
	 */
	async verifyMFA(factorId: string, code: string): Promise<Session> {
		const client = this.getClient();

		try {
			this.logger.info('Verifying MFA code...');

			// Create MFA challenge
			const { data: challengeData, error: challengeError } =
				await client.auth.mfa.challenge({ factorId });

			if (challengeError || !challengeData) {
				throw new AuthenticationError(
					`Failed to create MFA challenge: ${challengeError?.message || 'Unknown error'}`,
					'MFA_VERIFICATION_FAILED'
				);
			}

			// Verify the TOTP code
			const { data, error } = await client.auth.mfa.verify({
				factorId,
				challengeId: challengeData.id,
				code
			});

			if (error) {
				this.logger.error('MFA verification failed:', error);
				throw new AuthenticationError(
					`Invalid MFA code: ${error.message}`,
					'INVALID_MFA_CODE'
				);

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Re-run checkMFARequired() to get a fresh, verified factorId before calling verifyMFA.
  2. Ensure a valid AAL1 session exists (getSession() returns a session) before starting the MFA challenge.
  3. Have the user confirm the TOTP factor is still enrolled and verified in their account settings.
  4. Wait briefly and retry if rate-limited; otherwise check Supabase Auth logs for the challenge failure reason.

Example fix

// before
const mfa = await client.checkMFARequired();
await client.verifyMFA(cachedFactorId, code);
// after
const mfa = await client.checkMFARequired();
if (!mfa.required || !mfa.factorId) return; // no MFA needed
await client.verifyMFA(mfa.factorId, code); // fresh factorId every attempt
Defensive patterns

Strategy: validation

Validate before calling

const mfa = await client.checkMFARequired();
if (!mfa.required || typeof mfa.factorId !== 'string') {
  throw new Error('No verified MFA factor available for challenge');
}
// proceed to verifyMFA(mfa.factorId, code)

Type guard

function hasVerifiedFactor(m: { required: boolean; factorId?: string }): m is { required: true; factorId: string } {
  return m.required === true && typeof m.factorId === 'string' && m.factorId.length > 0;
}

Try / catch

try {
  await client.verifyMFA(factorId, code);
} catch (e) {
  if (e instanceof AuthenticationError && e.code === 'MFA_VERIFICATION_FAILED') {
    // refresh factor list via checkMFARequired() and re-challenge
    const fresh = await client.checkMFARequired();
    if (fresh.required && fresh.factorId) await client.verifyMFA(fresh.factorId, code);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling verifyMFA with an invalid/unverified factorId, a factor that was deleted or unenrolled, an AAL1 session that is expired (challenge requires an authenticated session), or a Supabase-side MFA error (rate limiting, 404 factor not found).

Common situations: User unenrolled TOTP after the CLI cached the factorId; passing a factorId from a different user/session; MFA disabled at project level; session expired between login and MFA prompt; requesting challenges too frequently and hitting rate limits.

Related errors


AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29). Data as JSON: /api/errors/f3a30d2a11d8a217. Report an issue: GitHub.