plandex-ai/plandex · warning

User {userId} is not a member of org {auth.OrgId}

Error message

User {userId} is not a member of org {auth.OrgId}

What it means

The membership check succeeded but returned false: the target userId is not a member of the authenticated org. The handler responds 403 'User <userId> is not a member of org <orgId>'. This is a caller-input problem — the userId path parameter points at someone outside (or removed from) this org.

Source

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

	if !auth.HasPermission(removePermission) {
		log.Printf("User does not have permission to remove user with role: %v\n", orgUser.OrgRoleId)
		http.Error(w, "User does not have permission to remove user with role: "+orgUser.OrgRoleId, http.StatusForbidden)
		return
	}

	// verify user is org member
	isMember, err := db.ValidateOrgMembership(userId, 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.Printf("User %s is not a member of org %s\n", userId, auth.OrgId)
		http.Error(w, "User "+userId+" is not a member of org "+auth.OrgId, http.StatusForbidden)
		return
	}

	orgOwnerRoleId, err := db.GetOrgOwnerRoleId()

	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)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Verify the target user is still a member of the org (SELECT * FROM org_users WHERE user_id = ... AND org_id = ...)
  2. Refresh the client's user list before deleting; treat 403 as 'not a member' and skip
  3. Ensure the caller's auth token belongs to the intended org
  4. If the user should be a member, re-add them via invite before deleting
Defensive patterns

Strategy: validation

Validate before calling

var member bool
err := db.Get(&member,
  "SELECT EXISTS(SELECT 1 FROM org_users WHERE user_id=$1 AND org_id=$2)",
  targetUserId, myOrgId)
if err == nil && !member {
    return errors.New("target is not a member of this org")
}

Type guard

func isOrgMember(db *sqlx.DB, userId, orgId string) bool {
    var ok bool
    _ = db.Get(&ok, "SELECT EXISTS(SELECT 1 FROM org_users WHERE user_id=$1 AND org_id=$2)", userId, orgId)
    return ok
}

Try / catch

if !isMember {
    http.Error(w, "User "+userId+" is not a member of org "+auth.OrgId, http.StatusForbidden)
    return
}
// client side:
// if resp.StatusCode == 403 && strings.HasPrefix(msg, "User ") { skip; refresh list }

Prevention

When it happens

Trigger: DELETE /users/<userId> where userId exists in the system but has no membership in auth.OrgId — wrong org id in token, stale client list, or the target already left/was removed.

Common situations: Client iterating a cached user list after the member was removed elsewhere; multi-org user whose membership in this org lapsed; calling the endpoint against the wrong org context; case-mismatched org id.

Related errors


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