plandex-ai/plandex · warning

Cannot delete the only org owner

Error message

Cannot delete the only org owner

What it means

A deliberate guard: the target user is an org owner and numOwners == 1, so deleting them would leave the org without any owner. The handler rejects with HTTP 403 'Cannot delete the only org owner'. This protects orgs from lockout of administrative actions.

Source

Thrown at app/server/handlers/users.go:183

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

	// verify user isn't the only org owner
	if orgUser.OrgRoleId == orgOwnerRoleId {
		numOwners, err := db.NumUsersWithRole(auth.OrgId, orgOwnerRoleId)

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

		if numOwners == 1 {
			log.Println("Cannot delete the only org owner")
			http.Error(w, "Cannot delete the only org owner", http.StatusForbidden)
			return
		}
	}

	err = db.WithTx(r.Context(), "delete org user", func(tx *sqlx.Tx) error {

		err = db.DeleteOrgUser(auth.OrgId, userId, tx)

		if err != nil {
			log.Println("Error deleting org user: ", err)
			return fmt.Errorf("error deleting org user: %v", err)
		}

		invite, err := db.GetActiveInviteByEmail(auth.OrgId, auth.User.Email)

		if err != nil {
			log.Println("Error getting invite for org user: ", err)
			return fmt.Errorf("error getting invite for org user: %v", err)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Promote another member to the owner role first, then delete the target owner
  2. If the caller is the last owner removing themselves, transfer ownership instead
  3. Adjust automation scripts to skip or handle users whose org_role_id equals the owner role id
  4. If the org should be decommissioned, delete the org itself rather than its last owner

Example fix

// before
DELETE /org-users/user-123  // user-123 is the only owner -> 403
// after
POST /org-users/owner-transfer {"newOwnerId": "user-456"}
DELETE /org-users/user-123  // now succeeds
Defensive patterns

Strategy: validation

Validate before calling

var ownerCount int
db.Get(&ownerCount,
  "SELECT COUNT(*) FROM org_users WHERE org_id=$1 AND org_role_id=$2",
  orgId, ownerRoleId)
if targetRoleId == ownerRoleId && ownerCount <= 1 {
    return errors.New("cannot remove the only org owner; transfer ownership first")
}

Type guard

func isSoleOwner(db *sqlx.DB, userId, orgId, ownerRoleId string) bool {
    var role string
    var n int
    _ = db.Get(&role, "SELECT org_role_id FROM org_users WHERE user_id=$1 AND org_id=$2", userId, orgId)
    if role != ownerRoleId { return false }
    _ = db.Get(&n, "SELECT COUNT(*) FROM org_users WHERE org_id=$1 AND org_role_id=$2", orgId, ownerRoleId)
    return n <= 1
}

Try / catch

if resp.StatusCode == http.StatusForbidden &&
   strings.Contains(body, "Cannot delete the only org owner") {
    // prompt user to promote a co-owner first, do not retry
    return errLastOwner
}

Prevention

When it happens

Trigger: DELETE request for the sole user holding the org owner role in the org — the only remaining owner is targeted for removal.

Common situations: Downsizing a team where the founder is the last owner; scripted cleanup scripts deleting users indiscriminately; owner trying to delete themselves when no co-owner exists.

Related errors


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