{"record":{"id":"f8d3b2a60aa04f62","repo":"zed-industries/zed","slug":"room-does-not-exist-or-was-already-joined","errorCode":null,"errorMessage":"room does not exist or was already joined","messagePattern":"room does not exist or was already joined","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/collab/src/db/queries/rooms.rs","lineNumber":342,"sourceCode":"                .filter(\n                    Condition::all()\n                        .add(room_participant::Column::RoomId.eq(room_id))\n                        .add(room_participant::Column::UserId.eq(user_id))\n                        .add(room_participant::Column::AnsweringConnectionId.is_null()),\n                )\n                .set(room_participant::ActiveModel {\n                    participant_index: ActiveValue::Set(Some(participant_index)),\n                    answering_connection_id: ActiveValue::set(Some(connection.id as i32)),\n                    answering_connection_server_id: ActiveValue::set(Some(ServerId(\n                        connection.owner_id as i32,\n                    ))),\n                    answering_connection_lost: ActiveValue::set(false),\n                    ..Default::default()\n                })\n                .exec(&*tx)\n                .await?;\n            if result.rows_affected == 0 {\n                Err(anyhow!(\"room does not exist or was already joined\"))?;\n            }\n\n            let room = self.get_room(room_id, &tx).await?;\n            Ok(JoinRoom {\n                room,\n                channel: None,\n            })\n        })\n        .await\n    }\n\n    pub async fn stale_room_connection(&self, user_id: UserId) -> Result<Option<ConnectionId>> {\n        self.transaction(|tx| async move {\n            let participant = room_participant::Entity::find()\n                .filter(room_participant::Column::UserId.eq(user_id))\n                .one(&*tx)\n                .await?;\n            Ok(participant.and_then(|p| p.answering_connection()))","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/collab/src/db/queries/rooms.rs#L324-L360","documentation":"Thrown by join_room when the UPDATE that fills in participant_index and answering_connection_id on the pending room_participant row affects zero rows. That row is created by call(); if it no longer matches, the invitation is no longer open: the caller cancelled, the ring timed out, or the row was already answered (possibly by another connection of the same user).","triggerScenarios":"Answering a ring with JoinRoom after the caller hit cancel; answering after the ring expiry cleanup removed the row; two devices of the called user answer simultaneously and the second UPDATE matches nothing; answering a ring from a room you were since removed from.","commonSituations":"Slow user reaction to an incoming call notification; notification shown on two devices; network latency delivering the join after the caller hung up.","solutions":["Handle this error by telling the user the call ended and dropping the incoming-call state; offer to call back","Ensure only one connection of the called user answers a given ring (pick one device, ignore the others)","Refresh room state (get_room / room_updated events) after this failure so the UI resynchronizes"],"exampleFix":"// before\nlet join = client.join_room(room_id, connection_id).await?;\n\n// after (recover from a dead ring)\nlet join = match client.join_room(room_id, connection_id).await {\n    Ok(join) => join,\n    Err(err) if err.to_string().contains(\"room does not exist or was already joined\") => {\n        self.incoming_calls.remove(&room_id);\n        self.ui.show_toast(\"Call was cancelled or already answered\");\n        return Ok(());\n    }\n    Err(err) => return Err(err),\n};","handlingStrategy":"fallback","validationCode":"// Only answer rings that are still live and not answered elsewhere.\nif let Some(ring) = self.incoming_calls.get(&room_id) {\n    if ring.connection_id == my_connection_id && !ring.answered {\n        client.join_room(room_id, my_connection_id).await?;\n        return Ok(());\n    }\n}\nlog::debug!(\"not answering stale ring for room {room_id}\");","typeGuard":null,"tryCatchPattern":"match client.join_room(room_id, my_connection_id).await {\n    Ok(join) => Ok(Some(join)),\n    Err(err) if err.to_string().contains(\"room does not exist or was already joined\") => {\n        self.incoming_calls.remove(&room_id);\n        self.ui.show_toast(\"Call was cancelled or already answered\");\n        Ok(None) // graceful fallback\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Answer a ring from exactly one connection/device","Drop incoming-call state when cancel events arrive; never join a dead ring","After this failure, resync room state before offering any retry"],"tags":["rooms","voice-calls","race-condition","join","server"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}