plandex-ai/plandex · warning · http
Invalid or expired pin
Error message
Invalid or expired pin
What it means
A 404 'Invalid or expired pin' is returned by CheckEmailVerificationHandler when db.ValidateEmailVerification returns the sentinel db.InvalidOrExpiredPinError. This means the submitted PIN does not match any active verification record for the email — it was mistyped, already used, or its TTL elapsed. Unlike the neighboring 500, this is an expected client-facing outcome, not a server fault.
Source
Thrown at app/server/handlers/sessions.go:162
log.Printf("Error reading request body: %v\n", err)
http.Error(w, "Error reading request body: "+err.Error(), http.StatusInternalServerError)
return
}
var req shared.VerifyEmailPinRequest
err = json.Unmarshal(body, &req)
if err != nil {
log.Printf("Error unmarshalling request: %v\n", err)
http.Error(w, "Error unmarshalling request: "+err.Error(), http.StatusInternalServerError)
return
}
req.Email = strings.ToLower(req.Email)
_, err = db.ValidateEmailVerification(req.Email, req.Pin)
if err != nil {
if err.Error() == db.InvalidOrExpiredPinError {
http.Error(w, "Invalid or expired pin", http.StatusNotFound)
return
}
log.Printf("Error validating email verification: %v\n", err)
http.Error(w, "Error validating email verification: "+err.Error(), http.StatusInternalServerError)
return
}
log.Println("Successfully verified email pin")
}
// sign in codes allow users to authenticate between different clients
// like UI to CLI or vice versa
func CreateSignInCodeHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Received request for CreateSignInCodeHandler")
auth := Authenticate(w, r, true)
View on GitHub (pinned to e2d772072e)
Solutions
- Request a fresh verification PIN and use the newest code from the most recent email
- Submit the PIN exactly as sent (6 alphanumeric chars, no whitespace)
- Ensure the email matches the one the PIN was issued to (case-insensitive)
- Check the email_verifications table for expiration timestamps if debugging server-side
- Automate re-issue on the client when a 404 invalid-pin response arrives
Example fix
// before: resubmitting a possibly stale pin forever
for {
err := verifyPin(email, pin)
if err != nil { /* retry same pin */ }
}
// after: request a fresh pin on invalid/expired 404
if resp.StatusCode == http.StatusNotFound {
if err := requestNewVerification(email); err != nil { return err }
pin = awaitNewPin(email)
} Defensive patterns
Strategy: fallback
Validate before calling
// client side: sanity-check before submitting
if len(pin) != 6 || strings.TrimSpace(pin) != pin {
return fmt.Errorf("pin must be exactly 6 characters with no whitespace")
} Try / catch
// Go client: treat 404 as expected, re-issue a pin
resp, err := http.Post(url, "application/json", body)
if err != nil { return err }
if resp.StatusCode == http.StatusNotFound {
// invalid or expired: request a fresh verification pin
return requestNewVerification(email)
} Prevention
- Auto-request a new PIN when a 404 invalid-pin response arrives
- Show a countdown of PIN expiry in the UI
- Discard older PINs when a new one is issued
- Strip whitespace from PIN input fields before submit
When it happens
Trigger: POST to verify-email-pin with a pin that: was typed incorrectly, was already consumed by a prior successful verification, belongs to an expired verification window, or does not match the row created for req.Email (email typo/case mismatch — server lowercases the email before lookup).
Common situations: User waits past the PIN expiration and retries an old code; user requests a new PIN and submits the older one; email client renders the PIN with extra whitespace; duplicate verification requests invalidate earlier codes.
Related errors
- error validating email verification: %v
- User not found
- error verifying email: %v
- error creating email verification: %v
- error prompting pin: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/80643ab207e8c2e2.
Report an issue: GitHub.