Mintplex-Labs/anything-llm · error · Error

Failed to generate user recovery codes!

Error message

Failed to generate user recovery codes!

What it means

Thrown by generateRecoveryCodes after RecoveryCode.createMany succeeded but the subsequent User._update to set seen_recovery_codes=true returned a falsy success. Recovery codes were already persisted, yet the user row could not be marked, so the function aborts to avoid returning codes whose 'seen' flag is inconsistent. It indicates a user-level write failure, not a recovery-code write failure.

Source

Thrown at server/utils/PasswordRecovery/index.js:28

  const newRecoveryCodes = [];
  const plainTextCodes = [];
  for (let i = 0; i < 4; i++) {
    const code = v4();
    const hashedCode = bcrypt.hashSync(code, 10);
    newRecoveryCodes.push({
      user_id: userId,
      code_hash: hashedCode,
    });
    plainTextCodes.push(code);
  }

  const { error } = await RecoveryCode.createMany(newRecoveryCodes);
  if (!!error) throw new Error(error);

  const { user: success } = await User._update(userId, {
    seen_recovery_codes: true,
  });
  if (!success) throw new Error("Failed to generate user recovery codes!");

  return plainTextCodes;
}

async function recoverAccount(username = "", recoveryCodes = []) {
  const user = await User.get({ username: String(username) });
  if (!user) return { success: false, error: "Invalid recovery codes." };

  // If hashes do not exist for a user
  // because this is a user who has not logged out and back in since upgrade.
  const allUserHashes = await RecoveryCode.hashesForUser(user.id);
  if (allUserHashes.length < 4)
    return { success: false, error: "Invalid recovery codes." };

  const uniqueRecoveryCodes = [
    ...new Set(
      recoveryCodes
        .map((code) => (typeof code === "string" ? code.trim() : ""))

View on GitHub (pinned to 526360e320)

Solutions

  1. Verify the userId passed to generateRecoveryCodes still exists in the users table at call time.
  2. Inspect DB logs/errors around the User._update call for the underlying failure.
  3. Treat this as a partial-write: the codes were created, so on retry ensure createMany is idempotent or clean up orphaned RecoveryCode rows for that user first.
  4. If the user was deleted intentionally, stop calling generateRecoveryCodes for that id.
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the user exists and is writable before generating codes.
const user = await User.get({ id: userId });
if (!user) {
  return { success: false, error: "User not found; cannot generate recovery codes." };
}

Try / catch

try {
  return await generateRecoveryCodes(userId);
} catch (e) {
  if (/Failed to generate user recovery codes/i.test(e.message)) {
    // Codes were persisted but the user flag was not — clean up to avoid orphan rows.
    await RecoveryCode.deleteMany({ user_id: userId }).catch(() => {});
    return { success: false, error: "Could not finalize recovery codes; please retry." };
  }
  throw e;
}

Prevention

When it happens

Trigger: The user record was deleted or its row locked between the two writes; the User model _update returned {user:false} due to a DB error or unknown user id; a concurrent session reset the user mid-flow.

Common situations: Account deletion racing with a login that triggers code generation; DB connection blip; passing a stale/invalid userId from a stale session token.

Related errors


AI-assisted analysis of Mintplex-Labs/anything-llm@526360e320 (2026-08-13). Data as JSON: /api/errors/620481d81bde9f7a. Report an issue: GitHub.