plandex-ai/plandex · info · http

User already exists

Error message

User already exists

What it means

CreateEmailVerificationHandler returns HTTP 409 'User already exists' when the request has no UserId, RequireNoUser is true, and an account already exists for the given email. RequireNoUser marks this as a signup-style flow, so issuing a verification pin would let someone hijack an existing account's email; the handler stops with Conflict.

Source

Thrown at app/server/handlers/sessions.go:79

			log.Printf("User not found for id: %v\n", req.UserId)
			http.Error(w, "User not found", http.StatusNotFound)
			return
		}

		if user.Email != req.Email {
			log.Printf("User email does not match for id: %v\n", req.UserId)
			http.Error(w, "User email does not match", http.StatusBadRequest)
			return
		}
	}

	if req.RequireUser && !hasAccount {
		log.Printf("User not found for email: %v\n", req.Email)
		http.Error(w, "User not found", http.StatusNotFound)
		return
	} else if req.RequireNoUser && hasAccount {
		log.Printf("User already exists for email: %v\n", req.Email)
		http.Error(w, "User already exists", http.StatusConflict)
		return
	}

	var res shared.CreateEmailVerificationResponse

	if !(os.Getenv("GOENV") == "development" && os.Getenv("LOCAL_MODE") == "1") {
		// create pin - 6 alphanumeric characters
		pinBytes, err := shared.GetRandomAlphanumeric(6)
		if err != nil {
			log.Printf("Error generating random pin: %v\n", err)
			http.Error(w, "Error generating random pin: "+err.Error(), http.StatusInternalServerError)
			return
		}

		// get sha256 hash of pin
		hashBytes := sha256.Sum256(pinBytes)
		pinHash := hex.EncodeToString(hashBytes[:])

View on GitHub (pinned to e2d772072e)

Solutions

  1. Route the user to the login flow instead: send the request with RequireUser=true (login-by-email-pin) rather than RequireNoUser.
  2. Offer a password reset / account recovery path since the email is already registered.
  3. In tests, use unique per-run emails (e.g., timestamped) or clean the users table between runs to avoid duplicate-account conflicts.
  4. If the existing account is orphaned/unwanted, delete it via an admin path, then retry signup.
  5. Normalize the email (lowercase/trim) client-side to avoid creating the duplicate in the first place.

Example fix

// before
req := shared.CreateEmailVerificationRequest{ Email: email, RequireNoUser: true }
// after
if accountExistsForEmail(email) {
    req = shared.CreateEmailVerificationRequest{ Email: email, RequireUser: true } // login instead
} else {
    req = shared.CreateEmailVerificationRequest{ Email: email, RequireNoUser: true }
}
Defensive patterns

Strategy: validation

Validate before calling

// prevent duplicate signups: check existence before a RequireNoUser request
if requireSignup && accountExistsForEmail(strings.ToLower(strings.TrimSpace(email))) {
    return ErrAccountExists // route to login/recovery instead of calling the API
}

Try / catch

resp, err := http.Post(url, "application/json", body)
if err != nil { return err }
if resp.StatusCode == http.StatusConflict {
    // email already registered: switch UI to login / password-reset flow
    return switchToLogin(email)
}

Prevention

When it happens

Trigger: POSTing CreateEmailVerificationRequest with empty UserId, RequireNoUser=true, and an email already present in the users table; a signup attempt for an email previously registered; a duplicate signup submission after the first one completed.

Common situations: User forgot they already have an account and tries to sign up again; password manager or client auto-resubmits a signup; user signs up with an email an org already provisioned; automated tests re-running against a persistent database with leftover users.

Related errors


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