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
- Treat rows_affected == 0 as idempotent success in the teardown path instead of Err (leaving twice is not a client-visible fault)
- Audit the caller to ensure the RPC is only sent once per (channel, connection) — check for duplicated leave requests on reconnect
- 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
- Send the leave-collaboration RPC exactly once per (channel, connection) pair
- On reconnect, generate a fresh connection id and rejoin rather than reusing stale cleanup callbacks
- Model teardown RPCs as idempotent on both client and server
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
- can't delete channel while a call is in progress
- no such member
- user is not a channel admin or channel does not exist
- user is not a channel member or channel does not exist
- user is not a channel participant or channel does not exist
AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16).
Data as JSON: /api/errors/bcf37c4cf0ce582d.
Report an issue: GitHub.