{"record":{"id":"51cc093230c3db93","repo":"zed-industries/zed","slug":"not-a-collaborator-on-this-project-51cc09","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/projects.rs","lineNumber":1025,"sourceCode":"        &self,\n        project_id: ProjectId,\n        connection: ConnectionId,\n    ) -> Result<TransactionGuard<(Option<proto::Room>, LeftProject)>> {\n        self.project_transaction(project_id, |tx| async move {\n            let result = project_collaborator::Entity::delete_many()\n                .filter(\n                    Condition::all()\n                        .add(project_collaborator::Column::ProjectId.eq(project_id))\n                        .add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))\n                        .add(\n                            project_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 project = project::Entity::find_by_id(project_id)\n                .one(&*tx)\n                .await?\n                .context(\"no such project\")?;\n            let collaborators = project\n                .find_related(project_collaborator::Entity)\n                .all(&*tx)\n                .await?;\n            let connection_ids: Vec<ConnectionId> = collaborators\n                .into_iter()\n                .map(|collaborator| collaborator.connection())\n                .collect();\n\n            follower::Entity::delete_many()\n                .filter(\n                    Condition::any()","sourceCodeStart":1007,"sourceCodeEnd":1043,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/collab/src/db/queries/projects.rs#L1007-L1043","documentation":"Thrown by leave_project when the DELETE of the project_collaborator row matching (project_id, connection.id, connection.owner_id) affects zero rows. The server can only remove a collaborator entry that was created by join_project for that exact connection on that exact server, so this error means the calling connection was never a collaborator or already left.","triggerScenarios":"Calling LeaveProject twice on the same connection (second call finds no row); calling leave from a different connection than the one that joined (e.g. another window/device); joining failed earlier and the client proceeds to leave anyway; the project was deleted concurrently.","commonSituations":"Cleanup paths that run on both window close and app shutdown, firing leave twice; multi-window clients where each window has its own connection; races between leaving and the server dropping the collaborator on connection loss.","solutions":["Make leave idempotent on the client: track joined state per connection and only call LeaveProject once per successful join","Ensure the same connection that issued JoinProject issues LeaveProject (do not share join state across connections)","Treat this error as benign in cleanup/teardown code: log it and continue shutting down"],"exampleFix":"// before\nself.client.leave_project(project_id).await?;\n\n// after (idempotent teardown)\nif self.joined_projects.remove(&project_id).is_some() {\n    if let Err(err) = self.client.leave_project(project_id).await {\n        if err.to_string().contains(\"not a collaborator\") {\n            log::debug!(\"already left project {project_id}\");\n        } else {\n            return Err(err);\n        }\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Track joins per connection; only leave what this connection joined.\nif self.joined_projects.remove(&project_id).is_none() {\n    log::debug!(\"skip leave_project({project_id}): not joined on this connection\");\n    return Ok(());\n}\nclient.leave_project(project_id).await?;","typeGuard":null,"tryCatchPattern":"match client.leave_project(project_id).await {\n    Ok(_) => {}\n    Err(err) if err.to_string().contains(\"not a collaborator\") => {\n        // already left / never joined from this connection; benign\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Make leave idempotent by tracking joined state per connection","Issue LeaveProject from the same connection that issued JoinProject","In teardown paths, log-and-continue on this error instead of failing shutdown"],"tags":["collab","lifecycle","idempotency","rpc","server"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}