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

no such invitation

Error message

no such invitation

What it means

Thrown in the ACCEPT branch of respond_to_channel_invite (crates/collab/src/db/queries/channels.rs:394): the code deletes the channel_member row matching (channel_id, user_id, accepted=false) and 0 rows were affected. Accepting an invite consumes the pending row, so 0 means there was no pending invitation for that user in that channel.

Source

Thrown at crates/collab/src/db/queries/channels.rs:394

            let membership_update = if accept {
                let rows_affected = channel_member::Entity::update_many()
                    .set(channel_member::ActiveModel {
                        accepted: ActiveValue::Set(accept),
                        ..Default::default()
                    })
                    .filter(
                        channel_member::Column::ChannelId
                            .eq(channel_id)
                            .and(channel_member::Column::UserId.eq(user_id))
                            .and(channel_member::Column::Accepted.eq(false)),
                    )
                    .exec(&*tx)
                    .await?
                    .rows_affected;

                if rows_affected == 0 {
                    Err(anyhow!("no such invitation"))?;
                }

                Some(
                    self.calculate_membership_updated(&channel, user_id, &tx)
                        .await?,
                )
            } else {
                let rows_affected = channel_member::Entity::delete_many()
                    .filter(
                        channel_member::Column::ChannelId
                            .eq(channel_id)
                            .and(channel_member::Column::UserId.eq(user_id))
                            .and(channel_member::Column::Accepted.eq(false)),
                    )
                    .exec(&*tx)
                    .await?
                    .rows_affected;
                if rows_affected == 0 {

View on GitHub (pinned to bc538def45)

Solutions

  1. Debounce/disable the accept button once the RPC is in flight, and mark the notification handled after the first success
  2. Before responding, fetch the user's pending invite (pending_invite_for_channel at channels.rs:784+) and skip if None
  3. On the server handler, consider mapping this error to an idempotent no-op when the member row already exists as accepted

Example fix

// before
db.respond_to_channel_invite(user_id, channel_id, true).await?;

// after
match db.pending_invite_for_channel(&channel, user_id, &tx).await? {
    Some(_) => { db.respond_to_channel_invite(user_id, channel_id, true).await?; }
    None => log::info!("invite already handled for channel {channel_id}"),
}
Defensive patterns

Strategy: validation

Validate before calling

// Only respond when a pending row exists for this user+channel
let pending = db
    .pending_invite_for_channel(&channel, user_id, &tx)
    .await?;
if pending.is_some() {
    db.respond_to_channel_invite(user_id, channel_id, true).await?;
}

Try / catch

// Racing accepts: loser treats already-handled as success
match db.respond_to_channel_invite(user_id, channel_id, true).await {
    Ok(result) => Some(result),
    Err(err) if err.to_string().contains("no such invitation") => None,
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Calling the invite-respond RPC with accept=true when the invite was already accepted (row now accepted=true), already declined (row deleted), revoked by an admin, or never existed. Two clients racing to accept the same invite (notification opened in two windows) — the loser gets this error.

Common situations: Stale notification UI letting the user click Accept on an already-handled invite; double-click/duplicate RPC from the client; retry logic re-sending an accepted respond request.

Related errors


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