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

  1. Request a fresh verification PIN and use the newest code from the most recent email
  2. Submit the PIN exactly as sent (6 alphanumeric chars, no whitespace)
  3. Ensure the email matches the one the PIN was issued to (case-insensitive)
  4. Check the email_verifications table for expiration timestamps if debugging server-side
  5. 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

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


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/80643ab207e8c2e2. Report an issue: GitHub.