zed-industries/zed · error · Error::Internal

not a collaborator on this project

Error message

not a collaborator on this project

What it means

Thrown by the collab server when removing a connection's channel_buffer_collaborator row affects zero rows (crates/collab/src/db/queries/buffers.rs:379). The DELETE is filtered on channel_id + connection.id + connection.owner_id, so the error means the (channel, connection) pair being torn down was never registered as a collaborator on that shared-project buffer — or was already removed moments earlier.

Source

Thrown at crates/collab/src/db/queries/buffers.rs:379

        &self,
        channel_id: ChannelId,
        connection: ConnectionId,
        tx: &DatabaseTransaction,
    ) -> Result<LeftChannelBuffer> {
        let result = channel_buffer_collaborator::Entity::delete_many()
            .filter(
                Condition::all()
                    .add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
                    .add(channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32))
                    .add(
                        channel_buffer_collaborator::Column::ConnectionServerId
                            .eq(connection.owner_id as i32),
                    ),
            )
            .exec(tx)
            .await?;
        if result.rows_affected == 0 {
            Err(anyhow!("not a collaborator on this project"))?;
        }

        let mut collaborators = Vec::new();
        let mut connections = Vec::new();
        let mut rows = channel_buffer_collaborator::Entity::find()
            .filter(
                Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
            )
            .stream(tx)
            .await?;
        while let Some(row) = rows.next().await {
            let row = row?;
            let connection = row.connection();
            connections.push(connection);
            collaborators.push(proto::Collaborator {
                peer_id: Some(connection.into()),
                replica_id: row.replica_id.0 as u32,
                user_id: row.user_id.to_proto(),

View on GitHub (pinned to bc538def45)

Solutions

  1. Treat rows_affected == 0 as idempotent success in the teardown path instead of Err (leaving twice is not a client-visible fault)
  2. Audit the caller to ensure the RPC is only sent once per (channel, connection) — check for duplicated leave requests on reconnect
  3. Verify the connection passed in is the same ConnectionId that opened the collaboration (same id AND owner_id), not a new connection for the same user

Example fix

// before
if result.rows_affected == 0 {
    Err(anyhow!("not a collaborator on this project"))?;
}

// after: idempotent teardown
if result.rows_affected == 0 {
    log::info!(
        "collaborator already removed for channel {channel_id}, connection {connection_id}"
    );
}
Defensive patterns

Strategy: try-catch

Try / catch

// Teardown paths: leaving twice is the desired end state
match db.remove_buffer_collaborator(channel_id, connection).await {
    Ok(_) => {}
    Err(err) if err.to_string().contains("not a collaborator") => {
        log::info!("collaborator already absent for channel {channel_id}");
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Calling the buffer-collaborator leave/cleanup path twice for the same connection (double disconnect race), removing collaboration for a connection that joined the project after the snapshot being mutated, or a connection whose server id (owner_id) no longer matches the row recorded at join time.

Common situations: Clients reconnecting and replaying a leave-collaboration RPC; server restarts where connection ids are reused; test harnesses that disconnect then manually invoke the cleanup query. Benign in production teardown paths but surfaces as a 500 if propagated to the RPC handler.

Related errors


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