zed-industries/zed · error

could not update room participant role

Error message

could not update room participant role

What it means

Thrown by set_room_participant_role when the UPDATE of role on the (room_id, user_id) participant row does not affect exactly 1 row. The target user must currently be a participant of the room; zero affected rows means they left, were kicked, or never joined.

Source

Thrown at crates/collab/src/db/queries/rooms.rs:1164

                self.check_user_has_signed_cla(user_id, room_id, &tx)
                    .await?;
            }

            let result = room_participant::Entity::update_many()
                .filter(
                    Condition::all()
                        .add(room_participant::Column::RoomId.eq(room_id))
                        .add(room_participant::Column::UserId.eq(user_id)),
                )
                .set(room_participant::ActiveModel {
                    role: ActiveValue::set(Some(role)),
                    ..Default::default()
                })
                .exec(&*tx)
                .await?;

            if result.rows_affected != 1 {
                Err(anyhow!("could not update room participant role"))?;
            }
            self.get_room(room_id, &tx).await
        })
        .await
    }

    async fn check_user_has_signed_cla(
        &self,
        user_id: UserId,
        room_id: RoomId,
        tx: &DatabaseTransaction,
    ) -> Result<()> {
        let channel = room::Entity::find_by_id(room_id)
            .one(tx)
            .await?
            .context("could not find room")?
            .find_related(channel::Entity)
            .one(tx)

View on GitHub (pinned to bc538def45)

Solutions

  1. Refresh the room's participant list immediately before issuing the role change and target only users still present
  2. Treat this error as 'user no longer in room' and update the admin UI accordingly
  3. If the intent is to pre-assign a role, invite the user (or have them rejoin) and then set the role

Example fix

// before
client.set_room_participant_role(room_id, target_user_id, new_role).await?;

// after (check membership first)
let room = client.get_room(room_id).await?;
let is_present = room.participants.iter().any(|p| p.user_id == target_user_id);
if !is_present {
    log::warn!("user {target_user_id} is no longer in room {room_id}; skipping role change");
    return Ok(());
}
client.set_room_participant_role(room_id, target_user_id, new_role).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Verify the target is still present before changing their role.
let room = client.get_room(room_id).await?;
if !room.participants.iter().any(|p| p.user_id == target_user_id) {
    log::warn!("target {target_user_id} not in room {room_id}; skipping role change");
    return Ok(());
}
client.set_room_participant_role(room_id, target_user_id, role).await?;

Type guard

fn is_room_participant(room: &proto::Room, user_id: u64) -> bool {
    room.participants.iter().any(|p| p.user_id == user_id)
}

Try / catch

match client.set_room_participant_role(room_id, target_user_id, role).await {
    Ok(room) => Ok(room),
    Err(err) if err.to_string().contains("could not update room participant role") => {
        self.refresh_participants(room_id); // target left; resync UI
        Ok(None)
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: Admin calls SetRoomParticipantRole for a user who just left the room or disconnected and was cleaned up; role change targeting a user_id that was never in the room; concurrent kick and role change racing so the row is deleted first.

Common situations: Admin UI acting on a stale participant list; moderation actions queued while the target user signs off; race between connection-loss cleanup and an admin's command.

Related errors


AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16). Data as JSON: /api/errors/be63a705e54d3c96. Report an issue: GitHub.