zed-industries/zed · error

cannot call a user who isn't a contact

Error message

cannot call a user who isn't a contact

What it means

Thrown by the call_user RPC handler when has_contact(calling_user_id, called_user_id) is false. Zed only lets you ring users with whom you have an established (mutually accepted) contact relationship; the check runs before the room/call machinery is touched.

Source

Thrown at crates/collab/src/rpc.rs:1754

/// Call someone else into the current room
async fn call(
    request: proto::Call,
    response: Response<proto::Call>,
    session: MessageContext,
) -> Result<()> {
    let room_id = RoomId::from_proto(request.room_id);
    let calling_user_id = session.user_id();
    let calling_connection_id = session.connection_id;
    let called_user_id = UserId::from_proto(request.called_user_id);
    let initial_project_id = request.initial_project_id.map(ProjectId::from_proto);
    if !session
        .db()
        .await
        .has_contact(calling_user_id, called_user_id)
        .await?
    {
        return Err(anyhow!("cannot call a user who isn't a contact"))?;
    }

    let incoming_call = {
        let (room, incoming_call) = &mut *session
            .db()
            .await
            .call(
                room_id,
                calling_user_id,
                calling_connection_id,
                called_user_id,
                initial_project_id,
            )
            .await?;
        room_updated(room, &session.peer);
        mem::take(incoming_call)
    };
    update_user_contacts(called_user_id, &session).await?;

View on GitHub (pinned to bc538def45)

Solutions

  1. Send a contact request (RequestContact) and wait for acceptance before calling; gate the call button on contact status
  2. If a request is already pending, wait for the recipient to accept rather than retrying the call
  3. Refresh contacts (the server pushes UpdateContacts) and only enable calls for accepted contacts

Example fix

// before
client.call_user(room_id, called_user_id, None).await?;

// after (check contact status first)
if !contacts.contains(&called_user_id) {
    client.request_contact(called_user_id).await?;
    self.ui.show_toast("Contact request sent; call once accepted");
    return Ok(());
}
client.call_user(room_id, called_user_id, None).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Only call accepted contacts.
if !self.contacts.contains(&called_user_id) {
    client.request_contact(called_user_id).await?;
    self.ui.show_toast("Contact request sent; call once accepted");
    return Ok(());
}
client.call_user(room_id, called_user_id, None).await?;

Type guard

fn can_call(contacts: &HashSet<UserId>, user_id: UserId) -> bool {
    contacts.contains(&user_id)
}

Try / catch

if let Err(err) = client.call_user(room_id, called_user_id, None).await {
    if err.to_string().contains("isn't a contact") {
        client.request_contact(called_user_id).await?; // convert to a contact request
        return Ok(());
    }
    return Err(err);
}

Prevention

When it happens

Trigger: CallUser sent to a user who never accepted your contact request (or whose request you never accepted); calling after the contact was removed on either side; calling a user id obtained from a room/channel rather than your contact list.

Common situations: UI exposing call buttons on channel members who are not contacts; contact request still pending; contact list out of sync after removing a contact.

Related errors


AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16). Data as JSON: /api/errors/8ea0358c0d549950. Report an issue: GitHub.