{"record":{"id":"1457eec67c152a04","repo":"zed-industries/zed","slug":"not-authorized-to-edit-projects","errorCode":null,"errorMessage":"not authorized to edit projects","messagePattern":"not authorized to edit projects","errorType":"validation","errorClass":"Error::Internal","httpStatus":null,"severity":"error","filePath":"crates/collab/src/db/queries/projects.rs","lineNumber":1137,"sourceCode":"            .context(\"no such project\")?;\n\n        let role_from_room = if let Some(room_id) = project.room_id {\n            room_participant::Entity::find()\n                .filter(room_participant::Column::RoomId.eq(room_id))\n                .filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id))\n                .one(tx)\n                .await?\n                .and_then(|participant| participant.role)\n        } else {\n            None\n        };\n\n        let role = role_from_room.unwrap_or(ChannelRole::Banned);\n\n        match capability {\n            Capability::ReadWrite => {\n                if !role.can_edit_projects() {\n                    return Err(anyhow!(\"not authorized to edit projects\"))?;\n                }\n            }\n            Capability::ReadOnly => {\n                if !role.can_read_projects() {\n                    return Err(anyhow!(\"not authorized to read projects\"))?;\n                }\n            }\n        }\n\n        Ok((project, role))\n    }\n\n    /// Returns the host connection for a read-only request to join a shared project.\n    pub async fn host_for_read_only_project_request(\n        &self,\n        project_id: ProjectId,\n        connection_id: ConnectionId,\n    ) -> Result<ConnectionId> {","sourceCodeStart":1119,"sourceCodeEnd":1155,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/collab/src/db/queries/projects.rs#L1119-L1155","documentation":"Thrown during project join authorization when the requested capability is Capability::ReadWrite but the caller's channel role cannot edit projects (role.can_edit_projects() is false). The role is taken from the caller's room_participant row; a missing participant row defaults to ChannelRole::Banned. Guest, Talker, and Banned roles are read-only in shared projects.","triggerScenarios":"JoinProject with read_write=true from a user whose room role is Guest or Talker (e.g. invited into a room as a guest); joining a project shared in a room the user was banned from (role defaults to Banned); channel rooms that assign guest roles to non-members.","commonSituations":"A guest invited to a call tries to edit files in the shared project; permission model changed between client and server versions; admin demoted a user to Talker mid-session and the client reconnects with read-write.","solutions":["Retry the join with Capability::ReadOnly when the caller only needs to view the project","Have a room Admin promote the user to Member/Admin so can_edit_projects() becomes true","Join the room through a normal invitation so the caller gets Member role rather than Guest"],"exampleFix":"// before\nlet (_, replica_id) = client.join_project(project_id, /* read_write */ true).await?;\n\n// after (fall back to read-only join)\nlet (_, replica_id) = match client.join_project(project_id, true).await {\n    Ok(joined) => joined,\n    Err(err) if err.to_string().contains(\"not authorized to edit projects\") => {\n        client.join_project(project_id, false).await?\n    }\n    Err(err) => return Err(err),\n};","handlingStrategy":"validation","validationCode":"// Derive the requested capability from the caller's room role.\nlet my_role = room.participants.iter()\n    .find(|p| p.user_id == my_user_id)\n    .and_then(|p| p.role)\n    .unwrap_or(proto::ChannelRole::Banned);\nlet can_edit = matches!(my_role, proto::ChannelRole::Admin | proto::ChannelRole::Member | proto::ChannelRole::Talker);\nlet capability = if can_edit { Capability::ReadWrite } else { Capability::ReadOnly };\nclient.join_project(project_id, capability).await?;","typeGuard":"fn can_edit_projects(role: proto::ChannelRole) -> bool {\n    use proto::ChannelRole::*;\n    matches!(role, Admin | Member | Talker)\n}","tryCatchPattern":"match client.join_project(project_id, true).await {\n    Ok(joined) => Ok(joined),\n    Err(err) if err.to_string().contains(\"not authorized to edit projects\") => {\n        client.join_project(project_id, false).await // read-only fallback\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Read your room role from the room state and choose the join capability accordingly","Default guests to read-only joins; escalate to read-write only after a role promotion","Listen for role-change events and re-evaluate capabilities before reconnect joins"],"tags":["authorization","roles","collab","join-project","server"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}