plandex-ai/plandex · error
error creating org user: %v
Error message
error creating org user: %v
What it means
AcceptInvite runs inside a WithTx transaction: after marking the invite accepted, it calls CreateOrgUser to add the invitee to the org. If CreateOrgUser fails (e.g. role or org rows missing, DB constraint, duplicate org_users row), the error is wrapped as "error creating org user: %v" and the whole transaction rolls back, so the invite stays unaccepted.
Source
Thrown at app/server/db/invite_helpers.go:129
return fmt.Errorf("error deleting invite: %v", err)
}
return nil
}
func AcceptInvite(ctx context.Context, invite *Invite, inviteeId string) error {
err := WithTx(ctx, "accept invite", func(tx *sqlx.Tx) error {
_, err := tx.Exec(`UPDATE invites SET accepted_at = NOW(), invitee_id = $1 WHERE id = $2`, inviteeId, invite.Id)
if err != nil {
return fmt.Errorf("error accepting invite: %v", err)
}
// create org user
err = CreateOrgUser(invite.OrgId, inviteeId, invite.OrgRoleId, tx)
if err != nil {
return fmt.Errorf("error creating org user: %v", err)
}
return nil
})
if err != nil {
return fmt.Errorf("error accepting invite: %v", err)
}
invite.InviteeId = &inviteeId
return nil
}
View on GitHub (pinned to e2d772072e)
Solutions
- Check the wrapped inner error for a uniqueness/duplicate-key violation — the user is likely already a member; treat as success or delete the org_users row and retry.
- Verify invite.OrgId and invite.OrgRoleId still exist in orgs/org_roles before accepting; refresh or re-issue the invite if not.
- Retry AcceptInvite — the transaction rolled back, so the invite is still pending and a transient DB error can simply be retried.
- Inspect CreateOrgUser directly with the given orgId, userId, roleId to reproduce the underlying insert error.
Example fix
// before
err = AcceptInvite(ctx, invite, userId)
if err != nil { return err }
// after
err = AcceptInvite(ctx, invite, userId)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
return nil // user already a member of the org
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
// before calling AcceptInvite
var orgExists, roleExists bool
if err := db.Conn.Get(&orgExists, "SELECT EXISTS(SELECT 1 FROM orgs WHERE id=$1)", invite.OrgId); err != nil || !orgExists {
return fmt.Errorf("invite org %s no longer exists", invite.OrgId)
}
if err := db.Conn.Get(&roleExists, "SELECT EXISTS(SELECT 1 FROM org_roles WHERE id=$1)", invite.OrgRoleId); err != nil || !roleExists {
return fmt.Errorf("invite role %s no longer exists", invite.OrgRoleId)
} Type guard
func inviteValid(inv *db.Invite) bool {
return inv != nil && inv.Id != "" && inv.OrgId != "" && inv.OrgRoleId != ""
} Try / catch
err := AcceptInvite(ctx, invite, userId)
if err != nil {
if strings.Contains(err.Error(), "error creating org user") && strings.Contains(err.Error(), "duplicate key") {
return nil // already a member
}
return fmt.Errorf("accept invite failed: %w", err)
} Prevention
- Check org_users for an existing membership before accepting to make the operation idempotent.
- Re-fetch the invite immediately before accepting to avoid acting on stale/deleted rows.
- Guard the accept endpoint against double-submits (idempotency key or UI disabling).
When it happens
Trigger: Calling AcceptInvite when invite.OrgId or invite.OrgRoleId references a deleted org/org_role, when the user already has an org_users row for that org (unique constraint), or when the org_users insert hits any DB error.
Common situations: Accepting a stale invite after the org was deleted; accepting the same invite twice concurrently so both transactions race to insert the org user; an org_role id captured on the invite no longer exists after role cleanup.
Related errors
- error creating invite: %v
- error deleting invite: %v
- error accepting invite: %v
- error starting transaction: %v
- error removing expired locks: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/c72a033b97e8d8de.
Report an issue: GitHub.