plandex-ai/plandex · warning

User is already a member of org

Error message

User is already a member of org

What it means

InviteUserHandler returns this 400 when db.ValidateOrgMembership reports that the user with the invited email already belongs to the target org. It is an expected, client-facing guard preventing duplicate invitations to existing members. The message means the invite is intentionally rejected, not that something failed.

Source

Thrown at app/server/handlers/invites.go:108

	if err != nil {
		log.Printf("Error getting user: %v\n", err)
		http.Error(w, "Error getting user: "+err.Error(), http.StatusInternalServerError)
		return
	}

	if user != nil {
		isMember, err := db.ValidateOrgMembership(user.Id, auth.OrgId)

		if err != nil {
			log.Printf("Error validating org membership: %v\n", err)
			http.Error(w, "Error validating org membership: "+err.Error(), http.StatusInternalServerError)
			return
		}

		if isMember {
			log.Println("User is already a member of org")
			http.Error(w, "User is already a member of org", http.StatusBadRequest)
			return
		}
	}

	// ensure invite isn't already active
	invite, err := db.GetActiveInviteByEmail(auth.OrgId, req.Email)

	if err != nil {
		log.Printf("Error getting invite: %v\n", err)
		http.Error(w, "Error getting invite: "+err.Error(), http.StatusInternalServerError)
		return
	}

	if invite != nil {
		log.Println("Invite already exists")
		http.Error(w, "Invite already exists", http.StatusBadRequest)
		return
	}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Treat the 400 as success from the admin's perspective: the user already has access
  2. Check the org's members list before sending invites to dedupe
  3. If the user should not be a member, remove them from the org first, then invite
  4. Surface a friendlier client message like 'This person is already in your org'

Example fix

// before
if isMember {
    http.Error(w, "User is already a member of org", http.StatusBadRequest)
    return
}
// after
if isMember {
    writeApiError(w, shared.ApiError{Type: shared.ApiErrorTypeOther, Status: http.StatusConflict, Msg: "User is already a member of this org"})
    return
}
Defensive patterns

Strategy: validation

Validate before calling

// client: check the org's member emails before inviting
memberEmails := map[string]bool{}
for _, m := range org.Members {
    memberEmails[strings.ToLower(m.Email)] = true
}
if memberEmails[strings.ToLower(email)] {
    return fmt.Errorf("%s is already a member of the org", email)
}

Type guard

func isAlreadyMemberError(statusCode int, body string) bool {
    return statusCode == http.StatusBadRequest && strings.Contains(body, "User is already a member of org")
}

Try / catch

resp, err := client.Post(inviteUrl, jsonBody)
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == 400 && strings.Contains(string(body), "already a member") {
    // not a failure: user already has access
    return nil
}

Prevention

When it happens

Trigger: POST to the invite-user endpoint whose req.Email matches an existing user account that is already an org member of auth.OrgId (e.g. the user signed up and joined via auto-add domain or accepted an earlier invite).

Common situations: Admins re-inviting a coworker who already joined; auto-add-domain orgs where the user already gained access through their email domain; retrying an invite after the invitee already accepted in another tab; case differences that bypass the client's dedupe check (server lowercases email but the admin typed a variant).

Related errors


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