Skip to content

fix: Check left guilds & clear expected guilds - #11547

Open
vakiliner wants to merge 18 commits into
discordjs:mainfrom
vakiliner:fix/left-guilds-cache-shard-reconnecting
Open

fix: Check left guilds & clear expected guilds#11547
vakiliner wants to merge 18 commits into
discordjs:mainfrom
vakiliner:fix/left-guilds-cache-shard-reconnecting

Conversation

@vakiliner

@vakiliner vakiliner commented May 28, 2026

Copy link
Copy Markdown
Contributor

This PR adds a check for cached guilds when receiving a gateway READY event.
If a cached guild is no longer available (i. e., has left) and is not sent in a gateway READY event, it is removed from the cache.
The expectedGuilds has been refactored and is now split into shards.

Fix #11546

@vercel

vercel Bot commented May 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
discord-js Skipped Skipped Aug 2, 2026 8:46pm
discord-js-guide Skipped Skipped Aug 2, 2026 8:46pm

Request Review

@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide May 28, 2026 23:12 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js May 28, 2026 23:12 Inactive
@vakiliner
vakiliner marked this pull request as ready for review May 29, 2026 11:22
@vakiliner
vakiliner requested a review from a team as a code owner May 29, 2026 11:22
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec1ed96b-d05c-446b-b6de-13fb76cc5fdf

📥 Commits

Reviewing files that changed from the base of the PR and between 3d61215 and c358ce4.

📒 Files selected for processing (6)
  • packages/discord.js/src/client/Client.js
  • packages/discord.js/src/client/actions/ActionsManager.js
  • packages/discord.js/src/client/actions/GuildDelete.js
  • packages/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
  • packages/discord.js/src/client/websocket/handlers/READY.js
  • packages/discord.js/typings/index.d.ts

📝 Walkthrough

Walkthrough

Adds a GuildDeleteAction that removes guild channels, destroys voice adapters, deletes the guild from cache, and emits GuildDelete; registers the action and calls it from GUILD_DELETE (unavailable) and READY handlers. expectedGuilds is changed to a per-shard Collection to track pending guilds.

Changes

Guild Deletion Flow

Layer / File(s) Summary
GuildDeleteAction definition and registration
packages/discord.js/src/client/actions/GuildDelete.js, packages/discord.js/src/client/actions/ActionsManager.js
New GuildDeleteAction class with handle(guild) that removes guild channels, destroys voice adapter, deletes the guild from client.guilds.cache, and emits Events.GuildDelete. Registered on ActionsManager as this.GuildDelete.
Websocket handler integration
packages/discord.js/src/client/websocket/handlers/GUILD_DELETE.js, packages/discord.js/src/client/websocket/handlers/READY.js
GUILD_DELETE handler now calls client.actions.GuildDelete.handle(guild) on unavailable guilds before returning. READY builds a per-READY expectedGuilds Set, stores it on client.expectedGuilds keyed by shardId, and calls client.actions.GuildDelete.handle(guild) for cached guilds on the shard not present in that set.
Per-shard expectedGuilds refactor
packages/discord.js/src/client/Client.js, packages/discord.js/typings/index.d.ts
Client#expectedGuilds changed from Set<Snowflake> to Collection<number, Set<Snowflake>>; code updated to remove received guild IDs from the shard-specific Set, delete empty shard entries, and aggregate unavailable-guild counts across shards in the readiness timeout debug message.

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: Check left guilds & clear expected guilds' clearly summarizes the main change: checking for guilds the bot has left and clearing the expectedGuilds set on READY.
Description check ✅ Passed The description accurately relates to the changeset, explaining that cached guilds are checked and removed on READY events, and expectedGuilds is cleared before adding new guilds.
Linked Issues check ✅ Passed The PR successfully addresses the requirements from #11546: it removes stale guilds from cache during READY events and clears expectedGuilds before repopulating it with fresh guild data from the gateway.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the left guilds caching issue: adding GuildDelete action, invoking it during GUILD_DELETE and READY handlers, with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/discord.js/src/client/websocket/handlers/READY.js`:
- Around line 24-27: The loop is evicting stale guilds by calling
client.actions.GuildDelete.handle(guild) directly so no public event is emitted;
change it to emit the public guild delete event (Events.GuildDelete) so
consumers observe the removal and still run the internal handler—i.e., for each
guild in client.guilds.cache not in client.expectedGuilds, call
client.emit(Events.GuildDelete, guild) (or call the action then emit if internal
logic must run first) instead of invoking client.actions.GuildDelete.handle
directly; locate the loop referencing client.guilds.cache,
client.expectedGuilds, and client.actions.GuildDelete.handle to apply this
change.
- Around line 17-27: The READY handler is clobbering client.expectedGuilds for
all shards and deleting guilds from client.guilds.cache regardless of their
shard; instead, scope expected-guild maintenance and stale-guild cleanup to the
current shardId: keep per-shard expected sets (e.g., client.expectedGuilds as a
Map keyed by shardId or store a Set at client.expectedGuilds.get(shardId)),
populate only that shard's Set when iterating data.guilds and set guild.shardId
= shardId before client.guilds._add(guild), and when scanning for stale guilds
only consider cached guilds with guild.shardId === shardId before calling
client.actions.GuildDelete.handle(guild). Ensure you do not clear the global
structure unconditionally and only remove entries for the current shard.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e6f2141a-59cd-4cbf-beb8-87e882e366fd

📥 Commits

Reviewing files that changed from the base of the PR and between 3d61215 and c3e5433.

📒 Files selected for processing (4)
  • packages/discord.js/src/client/actions/ActionsManager.js
  • packages/discord.js/src/client/actions/GuildDelete.js
  • packages/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
  • packages/discord.js/src/client/websocket/handlers/READY.js
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-03-30T11:04:39.419Z
Learnt from: almeidx
Repo: discordjs/discord.js PR: 11471
File: packages/discord.js/src/util/Util.js:83-86
Timestamp: 2026-03-30T11:04:39.419Z
Learning: For the discord.js and rest packages, follow the existing convention for constructing request URLs: use the `api` base URL and `version` string via raw interpolation (e.g., `${api}/v${version}${route}`) without adding normalization such as trimming trailing slashes on `api` or stripping/adding/remapping a leading `v` on `version`. Do not recommend changing this behavior during code review unless the existing pattern in this repo is intentionally being replaced.

Applied to files:

  • packages/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
  • packages/discord.js/src/client/actions/ActionsManager.js
  • packages/discord.js/src/client/actions/GuildDelete.js
  • packages/discord.js/src/client/websocket/handlers/READY.js
📚 Learning: 2026-05-18T13:40:11.014Z
Learnt from: almeidx
Repo: discordjs/discord.js PR: 11530
File: packages/core/src/api/user.ts:190-190
Timestamp: 2026-05-18T13:40:11.014Z
Learning: When reviewing discord.js (and related) source files, JSDoc `see` links that point to Discord’s documentation on `https://docs.discord.com/developers/...` are correct and should not be flagged as inconsistent. For new/updated links going forward, prefer `https://docs.discord.com/developers` over the legacy `https://discord.com/developers/docs` domain.

Applied to files:

  • packages/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
  • packages/discord.js/src/client/actions/ActionsManager.js
  • packages/discord.js/src/client/actions/GuildDelete.js
  • packages/discord.js/src/client/websocket/handlers/READY.js
📚 Learning: 2026-05-22T17:29:55.128Z
Learnt from: kshitijanurag
Repo: discordjs/discord.js PR: 11537
File: packages/discord.js/src/structures/Guild.js:1486-1495
Timestamp: 2026-05-22T17:29:55.128Z
Learning: When handling Discord Gateway opcode `RequestChannelInfo` (opcode name: `RequestChannelInfo` / payload gateway opcode), do not add or suggest a `nonce` field for correlating responses. This opcode’s payload supports only `guild_id` and `fields` (e.g., `fields: ["status", "voice_start_time"]`); ensure any review feedback reflects that schema and treats `nonce` as unsupported for this opcode.

Applied to files:

  • packages/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
  • packages/discord.js/src/client/actions/ActionsManager.js
  • packages/discord.js/src/client/actions/GuildDelete.js
  • packages/discord.js/src/client/websocket/handlers/READY.js
🔇 Additional comments (3)
packages/discord.js/src/client/actions/GuildDelete.js (1)

5-16: LGTM!

packages/discord.js/src/client/actions/ActionsManager.js (1)

22-22: LGTM!

packages/discord.js/src/client/websocket/handlers/GUILD_DELETE.js (1)

25-25: LGTM!

Comment thread packages/discord.js/src/client/websocket/handlers/READY.js Outdated
Comment thread packages/discord.js/src/client/websocket/handlers/READY.js
@vercel
vercel Bot temporarily deployed to Preview – discord-js May 29, 2026 16:36 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide May 29, 2026 16:36 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide May 29, 2026 16:39 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js May 29, 2026 16:39 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js May 29, 2026 17:19 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide May 29, 2026 17:19 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js May 29, 2026 18:35 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide July 10, 2026 11:54 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js July 10, 2026 11:54 Inactive

@Qjuh Qjuh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for the calling the GuildDelete action if missing from READY event looks fine, but I disagree with the other change since I don't see a benefit from separating the expectedGuilds by shardId.

@vakiliner

Copy link
Copy Markdown
Contributor Author

but I disagree with the other change since I don't see a benefit from separating the expectedGuilds by shardId

This is necessary if the connection suddenly breaks when not all expected guilds have been received in GUILD_CREATE.
If the bot leaves the guilds it's expecting in GUILD_CREATE, the next time it reconnects, the expected guild ID won't be cleared from the cache, which is not expected.

@vakiliner

vakiliner commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

but I disagree with the other change since I don't see a benefit from separating the expectedGuilds by shardId.

This bug doesn't occur in v14, as the cache is cleared in WebSocketShard.js.

this.expectedGuilds = new Set(packet.guilds.map(guild => guild.id));

@Qjuh

Qjuh commented Aug 2, 2026

Copy link
Copy Markdown
Member

This bug doesn't occur in v14, as the cache is cleared in WebSocketShard.js.

Ah, that explains the need for the split, thank you. But that sounds like the other half either can't happen or would exist on v14 anyway, as v14 doesn't do those artificial GUILD_DELETE events on reconnect.

Guess it needs someone to test if the issue even happens on v14 / if only the split is implemented in v15.

@vakiliner

Copy link
Copy Markdown
Contributor Author

Ah, that explains the need for the split, thank you. But that sounds like the other half either can't happen or would exist on v14 anyway, as v14 doesn't do those artificial GUILD_DELETE events on reconnect.

Guess it needs someone to test if the issue even happens on v14 / if only the split is implemented in v15.

I can do this.
To more easily reproduce the issue, you can ignore the following events after READY when you first connect, leave the guild that was expected, then reconnect (not resume), and observe the results.
Regarding v14, I meant that there is no error with the set of expected guilds, but there is an error with cached left guilds.

@vakiliner

vakiliner commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

So, I ran some testing.
In v14, expectedGuilds are ok, cached left guilds are not.
In v15, there are errors in both expectedGuilds and left guilds.
There are no errors in this pull request.

However, I did find one not-so-good behavior.
https://github.com/vakiliner/discord.js/blob/9e1dd2e522e54d24de923d55f0d57cec10e2053a/packages/discord.js/src/client/websocket/handlers/READY.js#L32
There's no check here that the guild was actually retrieved in GUILD_CREATE.
Should I emit partial guilds or omit them?

Okay, I mistakenly assumed that this was a partial guild, when in fact it is an unavailable guild.

@Qjuh

Qjuh commented Aug 2, 2026

Copy link
Copy Markdown
Member

There's no check here that the guild was actually retrieved in GUILD_CREATE.

Afaict the only case that would be relevant is if Client is used without Guilds intent. That's already an issue as many of discord.js methods rely on that intent. But you could check for the intent being present before deleting.

@vakiliner

Copy link
Copy Markdown
Contributor Author

But you could check for the intent being present before deleting.

If connect without the Guilds intent, then the GuildCreate and GuildDelete events are not provided, therefore I believe that it is possible not to emit guild delete event.

image

@vercel
vercel Bot temporarily deployed to Preview – discord-js August 2, 2026 17:28 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 2, 2026 17:28 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 2, 2026 17:31 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js August 2, 2026 17:31 Inactive
Comment thread packages/discord.js/src/client/actions/GuildDelete.js Outdated
@github-project-automation github-project-automation Bot moved this from Todo to Review in Progress in discord.js Aug 2, 2026
@vercel
vercel Bot temporarily deployed to Preview – discord-js August 2, 2026 19:50 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 2, 2026 19:50 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 2, 2026 20:34 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js August 2, 2026 20:34 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 2, 2026 20:46 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js August 2, 2026 20:46 Inactive
@vakiliner
vakiliner requested a review from Qjuh August 2, 2026 21:12
Comment thread packages/discord.js/src/client/websocket/handlers/READY.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Review in Progress

Development

Successfully merging this pull request may close these issues.

Left guilds remain in the cache during sharding reconnection

2 participants