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
- Debounce/disable the accept button once the RPC is in flight, and mark the notification handled after the first success
- Before responding, fetch the user's pending invite (pending_invite_for_channel at channels.rs:784+) and skip if None
- 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
- Mark invite notifications handled on first response and never re-accept
- Debounce duplicate clicks on accept/decline buttons
- Refresh the invite list from server state, not cache, before rendering actions
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
- not in a call
- can't delete channel while a call is in progress
- channel name can't be blank
- no such member
- user is not a channel admin or channel does not exist
AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16).
Data as JSON: /api/errors/bbd9d300d3ca4f72.
Report an issue: GitHub.