{"record":{"id":"5b71e8da2e431e75","repo":"zed-industries/zed","slug":"user-was-already-invited","errorCode":null,"errorMessage":"user was already invited","messagePattern":"user was already invited","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/call/src/call_impl/mod.rs","lineNumber":493,"sourceCode":"    }\n\n    pub fn global(cx: &App) -> Entity<Self> {\n        Self::try_global(cx).unwrap()\n    }\n\n    pub fn try_global(cx: &App) -> Option<Entity<Self>> {\n        let any = cx.try_global::<GlobalAnyActiveCall>()?;\n        any.0.entity().downcast::<Self>().ok()\n    }\n\n    pub fn invite(\n        &mut self,\n        called_user_id: u64,\n        initial_project: Option<Entity<Project>>,\n        cx: &mut Context<Self>,\n    ) -> Task<Result<()>> {\n        if !self.pending_invites.insert(called_user_id) {\n            return Task::ready(Err(anyhow!(\"user was already invited\")));\n        }\n        cx.notify();\n\n        if self._join_debouncer.running() {\n            return Task::ready(Ok(()));\n        }\n\n        let room = if let Some(room) = self.room().cloned() {\n            Some(Task::ready(Ok(room)).shared())\n        } else {\n            self.pending_room_creation.clone()\n        };\n\n        let invite = if let Some(room) = room {\n            cx.spawn(async move |_, cx| {\n                let room = room.await.map_err(|err| anyhow!(\"{err:?}\"))?;\n\n                let initial_project_id = if let Some(initial_project) = initial_project {","sourceCodeStart":475,"sourceCodeEnd":511,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/call/src/call_impl/mod.rs#L475-L511","documentation":"ActiveCall::invite de-duplicates concurrent invites with a pending_invites set; inserting a called_user_id that is already pending returns this error immediately from a ready task, without contacting the server.","triggerScenarios":"Calling invite(user_id, ...) twice before the first invite task completes - double-click on a call button, duplicated UI events, or programmatic retries that do not await the first Task.","commonSituations":"UI buttons firing twice; retry wrappers re-invoking invite without awaiting; rapid successive calls to the same person.","solutions":["Await (or track) the first invite task before issuing another for the same user.","Disable or debounce the invite button while an invite is pending.","Treat the error as an idempotent no-op when the intent was a duplicate retry."],"exampleFix":"// before: fire-and-forget, duplicate clicks double-invite\ncall.update(cx, |c, cx| c.invite(user_id, project, cx));\n\n// after: track in-flight invites and short-circuit duplicates\nif invite_in_flight.contains(&user_id) {\n    return Task::ready(Ok(()));\n}\ninvite_in_flight.insert(user_id);\ncall.update(cx, |c, cx| c.invite(user_id, project, cx))","handlingStrategy":"validation","validationCode":"if invite_in_flight.contains(&called_user_id) {\n    return Task::ready(Ok(())); // idempotent no-op\n}\ninvite_in_flight.insert(called_user_id);\nlet task = call.update(cx, |call, cx| call.invite(called_user_id, project, cx));\n// remove from invite_in_flight when the task completes","typeGuard":null,"tryCatchPattern":"match invite-task errors containing \"user was already invited\" and map them to Ok(()) - the invite is already in flight, so treat it as success in the UI.","preventionTips":["Disable invite buttons while the invite task runs","Track in-flight invites per user in UI state","Await the first invite task before retrying"],"tags":["call","invite","duplicate","race-condition"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}