zed-industries/zed · error · Error::Internal

contact already requested

Error message

contact already requested

What it means

Thrown by send_contact_request (crates/collab/src/db/queries/contacts.rs:172): the INSERT uses ON CONFLICT (user_id_a, user_id_b) that would auto-accept the other user's pending request, but only when the existing row is pending AND directed from the other user (action_and_where at contacts.rs:156-164). rows_affected == 0 means a row for the pair already exists in any other state: the sender already requested (same direction), or the pair are already accepted contacts.

Source

Thrown at crates/collab/src/db/queries/contacts.rs:172

                        (contact::Column::ShouldNotify, false.into()),
                    ])
                    .action_and_where(
                        contact::Column::Accepted.eq(false).and(
                            contact::Column::AToB
                                .eq(a_to_b)
                                .and(contact::Column::UserIdA.eq(id_b))
                                .or(contact::Column::AToB
                                    .ne(a_to_b)
                                    .and(contact::Column::UserIdA.eq(id_a))),
                        ),
                    )
                    .to_owned(),
            )
            .exec_without_returning(&*tx)
            .await?;

            if rows_affected == 0 {
                Err(anyhow!("contact already requested"))?;
            }

            Ok(self
                .create_notification(
                    receiver_id,
                    rpc::Notification::ContactRequest {
                        sender_id: sender_id.to_proto(),
                    },
                    true,
                    &tx,
                )
                .await?
                .into_iter()
                .collect())
        })
        .await
    }

View on GitHub (pinned to bc538def45)

Solutions

  1. Track sent-request state client-side and hide/disable the request action once pending (the notification flow will surface acceptance)
  2. If the pair are already contacts, skip sending; has_contact (contacts.rs:107) tells you the accepted state
  3. On the server handler, consider mapping this error to a friendly 'request already pending' response instead of a generic 500

Example fix

// before
client.send_contact_request(my_id, their_id).await?; // can fire twice

// after
if !outgoing_requests.contains(&their_id) && !contacts.contains(&their_id) {
    client.send_contact_request(my_id, their_id).await?;
    outgoing_requests.insert(their_id);
}
Defensive patterns

Strategy: validation

Validate before calling

// Check relationship state before requesting
if db.has_contact(sender_id, receiver_id).await? {
    return Ok(()); // already contacts
}
if outgoing_pending_requests.contains(&receiver_id) {
    return Ok(()); // already requested
}
db.send_contact_request(sender_id, receiver_id).await?;

Try / catch

match db.send_contact_request(sender_id, receiver_id).await {
    Ok(batch) => Ok(batch),
    Err(err) if err.to_string().contains("contact already requested") => {
        Err(anyhow!("contact request already pending or already contacts"))
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: User A sends a contact request to B twice (the pending row already points A→B, so the conflict-update doesn't match); A requests B when A and B are already accepted contacts; A re-sends after B already requested A but the row was since accepted.

Common situations: Client UI not disabling the 'add contact' button after a request is sent; retry logic duplicating the request RPC; both effects — user re-taps because the first response was slow.

Related errors


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