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 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.
Source
Thrown at crates/collab/src/db/queries/projects.rs:1025
&self,
project_id: ProjectId,
connection: ConnectionId,
) -> Result<TransactionGuard<(Option<proto::Room>, LeftProject)>> {
self.project_transaction(project_id, |tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
Condition::all()
.add(project_collaborator::Column::ProjectId.eq(project_id))
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
project_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 project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.context("no such project")?;
let collaborators = project
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
let connection_ids: Vec<ConnectionId> = collaborators
.into_iter()
.map(|collaborator| collaborator.connection())
.collect();
follower::Entity::delete_many()
.filter(
Condition::any()View on GitHub (pinned to bc538def45)
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
Example fix
// before
self.client.leave_project(project_id).await?;
// after (idempotent teardown)
if self.joined_projects.remove(&project_id).is_some() {
if let Err(err) = self.client.leave_project(project_id).await {
if err.to_string().contains("not a collaborator") {
log::debug!("already left project {project_id}");
} else {
return Err(err);
}
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track joins per connection; only leave what this connection joined.
if self.joined_projects.remove(&project_id).is_none() {
log::debug!("skip leave_project({project_id}): not joined on this connection");
return Ok(());
}
client.leave_project(project_id).await?; Try / catch
match client.leave_project(project_id).await {
Ok(_) => {}
Err(err) if err.to_string().contains("not a collaborator") => {
// already left / never joined from this connection; benign
}
Err(err) => return Err(err),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- not a collaborator on this project
- no such member
- not authorized to edit projects
- cannot call a user who isn't a contact
- failed to ring user
AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16).
Data as JSON: /api/errors/51cc093230c3db93.
Report an issue: GitHub.