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
- Re-run checkMFARequired() to get a fresh, verified factorId before calling verifyMFA.
- Ensure a valid AAL1 session exists (getSession() returns a session) before starting the MFA challenge.
- Have the user confirm the TOTP factor is still enrolled and verified in their account settings.
- 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
- Always fetch a fresh factorId via checkMFARequired() instead of caching it.
- Ensure a valid AAL1 session exists before starting the MFA challenge.
- Handle user factor changes (unenroll/re-enroll) between sessions.
- Avoid rapid repeated challenges to stay under Supabase rate limits.
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.