{"record":{"id":"f4d571d01c85509e","repo":"plandex-ai/plandex","slug":"invite-already-exists","errorCode":null,"errorMessage":"Invite already exists","messagePattern":"Invite already exists","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"app/server/handlers/invites.go","lineNumber":124,"sourceCode":"\t\tif isMember {\n\t\t\tlog.Println(\"User is already a member of org\")\n\t\t\thttp.Error(w, \"User is already a member of org\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// ensure invite isn't already active\n\tinvite, err := db.GetActiveInviteByEmail(auth.OrgId, req.Email)\n\n\tif err != nil {\n\t\tlog.Printf(\"Error getting invite: %v\\n\", err)\n\t\thttp.Error(w, \"Error getting invite: \"+err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif invite != nil {\n\t\tlog.Println(\"Invite already exists\")\n\t\thttp.Error(w, \"Invite already exists\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\terr = db.WithTx(r.Context(), \"invite user\", func(tx *sqlx.Tx) error {\n\n\t\terr = db.CreateInvite(&db.Invite{\n\t\t\tOrgId:     auth.OrgId,\n\t\t\tOrgRoleId: req.OrgRoleId,\n\t\t\tEmail:     req.Email,\n\t\t\tName:      req.Name,\n\t\t\tInviterId: currentUserId,\n\t\t}, tx)\n\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error creating invite: %v\\n\", err)\n\t\t\treturn fmt.Errorf(\"error creating invite: %v\", err)\n\t\t}\n","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/invites.go#L106-L142","documentation":"InviteUserHandler returns this 400 when db.GetActiveInviteByEmail finds an already-active invite for the same org and email. It is a deliberate idempotency guard: only one pending invite per email per org is allowed. The request is rejected before any new invite row is created.","triggerScenarios":"POST to the invite-user endpoint with an email that already has a pending (not yet accepted, expired, or deleted) invite in auth.OrgId; re-inviting someone before they accept; resending an invite to an unaccepted email.","commonSituations":"Admins clicking 'invite' twice; wanting to resend an invitation but the old one is still pending; a stale pending invite from a previous teammate; the invited person's email was entered slightly differently previously (aliases, case handled by lowercase normalization).","solutions":["If you want to resend, delete the existing pending invite first (DeleteInviteHandler) then invite again","Treat the 400 as a no-op: an invite is already waiting in the invitee's inbox","Check the pending invites list (ListPendingInvitesHandler) before re-inviting","Client-side: catch this 400 and show 'An invite for this email is already pending'"],"exampleFix":"// before\nif invite != nil {\n    http.Error(w, \"Invite already exists\", http.StatusBadRequest)\n    return\n}\n// after\nif invite != nil {\n    writeApiError(w, shared.ApiError{Type: shared.ApiErrorTypeOther, Status: http.StatusConflict, Msg: \"An active invite for this email already exists\"})\n    return\n}","handlingStrategy":"validation","validationCode":"// client: look for an existing active invite before calling invite again\ninvites, err := listPendingInvites(orgId)\nif err == nil {\n    for _, inv := range invites {\n        if strings.ToLower(inv.Email) == strings.ToLower(email) {\n            return fmt.Errorf(\"an active invite for %s already exists\", email)\n        }\n    }\n}","typeGuard":"func isInviteAlreadyExistsError(statusCode int, body string) bool {\n    return statusCode == http.StatusBadRequest && strings.Contains(body, \"Invite already exists\")\n}","tryCatchPattern":"resp, err := client.Post(inviteUrl, jsonBody)\nbody, _ := io.ReadAll(resp.Body)\nif resp.StatusCode == 400 && strings.Contains(string(body), \"Invite already exists\") {\n    // idempotent outcome: delete the old invite first if a resend is intended\n    return nil\n}","preventionTips":["Disable the invite button in the UI while an invite for that email is pending","Delete the existing pending invite (DELETE invite endpoint) before resending","Cache known pending invite emails client-side","Treat this 400 as success in retry/idempotency logic"],"tags":["invites","validation","http-400","duplicate"],"backgroundTag":"duplicate-resource-conflict","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}