Edit history

Sharded bot loses a guild after kick and re-invite because the presence only knows one shard's guilds has not been edited, so there are no earlier versions.

Current version | Original by invaliduser231
Show

Sharded bot loses a guild after kick and re-invite because the presence only knows one shard's guilds

Reproduction steps

  1. Run a bot with 2 shards (shard: [0, 2] and shard: [1, 2]) and keep both sessions connected.
  2. Take a guild that belongs to the shard whose session identified second, so the guild is missing from the presence guild_ids.
  3. Kick the bot from that guild.
  4. Invite the bot to the guild again.
  5. Send a message in the guild.
Happens every time for guilds of the shard that did not create the presence. See the reproduction against the gateway code under evidence.

Expected behaviour

The owning shard receives GUILD_DELETE for the kick and GUILD_CREATE for the re-invite, and gets the events of the guild again.

Actual behaviour

The owning shard receives neither GUILD_DELETE nor GUILD_CREATE. Only the other shard gets GUILD_CREATE. After that no events of the guild reach the owning shard, so the bot stops responding there until that shard identifies again.

Build information

Canary Desktop 2026.1026.22540, Web 2026.1027.182052, Windows 11 10.0.26300 (x64), Electron 44.9.1, Chrome 152.0.7977.167, Node 24.22.0, Locale en-GB

Platform

API

Evidence

Summary

With a sharded bot, the user presence holds only the guild IDs of the session that created it. When the bot is kicked from a guild of another shard and invited again, the owning shard never gets the guild back. It receives no events for that guild until it identifies again.

Setup

  • Bot with 2 shards (shard: [0, 2] and shard: [1, 2]), about 2100 guilds
  • Both sessions stay connected for days, with resumes and the occasional fresh identify on either side

What happened

Guild 1488115521506995110 belongs to shard 0 ((id >> 22) % 2 == 0). A moderator kicked the bot and invited it again a few minutes later.
  • Shard 1 received GUILD_CREATE for the guild at 2026-10-28T09:17:44Z. The member's joined_at matches to the second.
  • Shard 0 received neither GUILD_DELETE for the kick nor GUILD_CREATE for the re-join.
  • From then on no MESSAGE_CREATE or other event of that guild reached shard 0, so the bot stopped responding there.
  • I restarted shard 0. A fresh identify builds the guild list from the database again, so the guild is back on the right session.

Cause

  1. The presence takes its guild_ids from the session that starts it. That list is already filtered to the session's shard:
  2. Later sessions of the same user adopt the running presence. Their guild IDs are never merged in:
  3. On kick, handle_leave_guild does not find the guild in guild_ids, so no guild_leave goes to the sessions:
  4. The guild process still drops every session of the kicked user:
  5. On re-join, handle_join_guild adds the guild and sends guild_join to all sessions:
The result is that the guild is only connected on the shard that does not own it. A kick on its own is affected too. Every session of a shard whose guilds are missing from the presence keeps the guild after the bot was removed, and never sees GUILD_DELETE.

Reproduced against the gateway code

I compiled fluxer_gateway at 7e30e95 in erlang:28.5.0.8-slim (NIF build skipped) and ran the steps above with the real modules: gateway_sharding, session_visits, presence_manager_shard with a real presence process, guild_state_member and session. Only the two bot sessions are stand-in processes that record what they receive.
== 1. identify filter (gateway_sharding)
shard 0 guilds: [1488115521506995110,1474874137937518680]
shard 1 guilds: [1485394316330348923,1564551479656722433]
== 2. presence guild_ids (session_visits:member_guild_ids + presence_manager_shard)
shard 1 then shard 0 get the same presence: true
presence guild_ids: [1485394316330348923,1564551479656722433]
== 3. kick from shard 0 guild 1488115521506995110 (presence leave_guild cast)
shard 0 session got: []
shard 1 session got: []
   control: kick from shard 1 guild 1485394316330348923
shard 0 session got: [{guild_leave,1485394316330348923}]
shard 1 session got: [{guild_leave,1485394316330348923}]
== 4. guild process removes the kicked member's sessions (guild_state_member)
guild sessions before: [<<"other-user">>,<<"session-shard-0">>]
guild sessions after:  [<<"other-user">>]
messages the shard 0 session got from this: []
== 5. re-invite (presence join_guild cast)
shard 0 session got: [{guild_join,1488115521506995110}]
shard 1 session got: [{guild_join,1488115521506995110}]
== 6. sessions handle guild_join (session:handle_cast + handle_info guild_connect)
shard 0 session: #{connect_started => false, guild_entry => {<0.88.0>,#Ref<...>}, state_unchanged => true}
shard 1 session: #{connect_started => true, guild_entry => no_entry_yet, state_unchanged => false}
The full test module is attached: repro.erl. Run it with erl -noshell -pa _build/default/lib/*/ebin -pa . -eval "repro:run(), halt()." after rebar3 compile.

Possible fixes

  • Merge the session's guild_ids into the presence when a session adopts an existing presence. Removing them again when a session disconnects would also be needed, otherwise a guild that only one session knows can stay in the list. Either way the presence should hold the union of all its sessions' guilds.
  • In addition or instead, handle_guild_connect could check whether the guild process still has the session subscribed before it treats an existing {Pid, Ref} as connected.