{"record":{"id":"bcf37c4cf0ce582d","repo":"zed-industries/zed","slug":"not-a-collaborator-on-this-project","errorCode":null,"errorMessage":"not a collaborator on this project","messagePattern":"not a collaborator on this project","errorType":"validation","errorClass":"Error::Internal","httpStatus":null,"severity":"error","filePath":"crates/collab/src/db/queries/buffers.rs","lineNumber":379,"sourceCode":"        &self,\n        channel_id: ChannelId,\n        connection: ConnectionId,\n        tx: &DatabaseTransaction,\n    ) -> Result<LeftChannelBuffer> {\n        let result = channel_buffer_collaborator::Entity::delete_many()\n            .filter(\n                Condition::all()\n                    .add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))\n                    .add(channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32))\n                    .add(\n                        channel_buffer_collaborator::Column::ConnectionServerId\n                            .eq(connection.owner_id as i32),\n                    ),\n            )\n            .exec(tx)\n            .await?;\n        if result.rows_affected == 0 {\n            Err(anyhow!(\"not a collaborator on this project\"))?;\n        }\n\n        let mut collaborators = Vec::new();\n        let mut connections = Vec::new();\n        let mut rows = channel_buffer_collaborator::Entity::find()\n            .filter(\n                Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),\n            )\n            .stream(tx)\n            .await?;\n        while let Some(row) = rows.next().await {\n            let row = row?;\n            let connection = row.connection();\n            connections.push(connection);\n            collaborators.push(proto::Collaborator {\n                peer_id: Some(connection.into()),\n                replica_id: row.replica_id.0 as u32,\n                user_id: row.user_id.to_proto(),","sourceCodeStart":361,"sourceCodeEnd":397,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/collab/src/db/queries/buffers.rs#L361-L397","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif result.rows_affected == 0 {\n    Err(anyhow!(\"not a collaborator on this project\"))?;\n}\n\n// after: idempotent teardown\nif result.rows_affected == 0 {\n    log::info!(\n        \"collaborator already removed for channel {channel_id}, connection {connection_id}\"\n    );\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Teardown paths: leaving twice is the desired end state\nmatch db.remove_buffer_collaborator(channel_id, connection).await {\n    Ok(_) => {}\n    Err(err) if err.to_string().contains(\"not a collaborator\") => {\n        log::info!(\"collaborator already absent for channel {channel_id}\");\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["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"],"tags":["collab","database","authorization","idempotency"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}