gravitational/teleport · error
max retry attempts reached: %w
Error message
max retry attempts reached: %w
What it means
The FIDO2 device callback wrapper retries transient failures (e.g. touch required, wrong PIN attempts) up to a bounded number of attempts. When the retry budget is exhausted while the last attempt still returned an error, it returns "max retry attempts reached" wrapping the most recent error. This prevents infinite retry loops against misbehaving or repeatedly-failing devices.
Source
Thrown at lib/auth/webauthncli/fido2.go:1025
// See https://github.com/Yubico/libfido2/blob/main/src/fido/err.h#L32.
switch fidoErr.Code {
case 60: // FIDO_ERR_UV_BLOCKED, 0x3c
const msg = "" +
"The user verification function in your security key is blocked. " +
"This is likely due to too many failed authentication attempts. " +
"Consult your manufacturer documentation for how to unblock your security key. " +
"Alternatively, you may unblock your device by using it in the Web UI."
return trace.Wrap(err, msg)
case 63: // FIDO_ERR_UV_INVALID, 0x3f
fidoLog.DebugContext(context.Background(), "Retrying libfido2 error 63")
continue
default: // Unexpected code.
return err
}
}
return fmt.Errorf("max retry attempts reached: %w", err)
}
}
func withPINHandler(cb deviceCallbackFunc) pinAwareCallbackFunc {
return func(dev FIDODevice, info *deviceInfo, pin string) (requiresPIN bool, err error) {
// Attempt to select a device by running "deviceCallback" on it.
// For most scenarios this works, saving a touch.
err = cb(dev, info, pin)
switch {
case errors.Is(err, libfido2.ErrPinRequired):
// Continued below.
case errors.Is(err, libfido2.ErrUnsupportedOption) && pin == "" && !info.uv && info.clientPinSet:
// The failing option is likely to be "UV", so we handle this the same as
// ErrPinRequired: see if the user selects this device, ask for the PIN and
// try again.
// Continued below.
default:
returnView on GitHub (pinned to 1283425b60)
Solutions
- Wait for the UV-blocked/cooldown period to expire and retry with the correct PIN.
- Replace retry loops with user interaction: prompt the user to touch the key / re-enter PIN between attempts.
- Unplug and reinsert the security key (or reset it if necessary) to clear a stuck state.
- Increase the max retry count in the retry wrapper only if the operation is known to need more attempts, and add backoff between retries.
Example fix
// before
for i := 0; i < maxRetries; i++ {
return deviceCallback(dev) // fails repeatedly, no backoff
}
// after
for i := 0; i < maxRetries; i++ {
err := deviceCallback(dev)
if err == nil { return nil }
time.Sleep(backoff(i)) // give the device/user time to recover
} Defensive patterns
Strategy: retry
Validate before calling
info, err := dev.Info()
if err != nil { return err }
if info.AuthenticatorConfig != nil && uvBlocked(info) {
return errors.New("device UV is temporarily blocked; wait before retrying")
} Type guard
func isRetryableFIDO2Error(err error) bool {
var le *libfido2.Error
if errors.As(err, &le) {
switch le.Code {
case libfido2.ErrTouchRequired, libfido2.ErrPinInvalid, libfido2.ErrPinAuthBlocked:
return le.Code != libfido2.ErrPinAuthBlocked // blocked needs cooldown, not blind retry
}
}
return false
} Try / catch
assertion, err := fido2Login(ctx, cfg, user, prompt)
if err != nil {
if strings.Contains(err.Error(), "max retry attempts reached") {
return trace.Wrap(err, "ask the user to touch the key or re-enter the correct PIN, then retry")
}
return trace.Wrap(err)
} Prevention
- Prompt for user touch/PIN between attempts instead of tight loops.
- Respect UV-blocked cooldowns (err 0x37) — wait before retrying, don't hammer the device.
- Apply backoff in any custom deviceCallback retry wrapper.
- Keep the security key firmware/driver (libfido2) up to date to reduce transient failures.
When it happens
Trigger: A fido2 device operation (login/register callback) fails repeatedly with a retryable status (e.g. FIDO_ERR_UV_BLOCKED, wrong PIN, device busy) for the maximum number of attempts, so the loop exits via the final return.
Common situations: User entering the wrong FIDO2 PIN repeatedly until UV is blocked; a security key left in a busy/unresponsive state; device drivers or libfido2 returning transient errors continuously; automated scripts hammering a device without human touch.
Related errors
- no credentials for user %q
- you are using a security key that is not registered with Tel
- device already holds a registered credential
- device not registered for passwordless
- device cannot fulfill platform attachment requirement
AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02).
Data as JSON: /api/errors/68a6aa7eb9335b32.
Report an issue: GitHub.