Skip to content

fix(plugin-gateway): forward the guildDelete of a guild READY drops - #247

Merged
kodiakhq[bot] merged 2 commits into
mainfrom
fix/plugin-gateway-forward-ready-guild-delete
Oct 17, 2026
Merged

kodiakhq[bot] merged 2 commits into
mainfrom
fix/plugin-gateway-forward-ready-guild-delete

Conversation

@RedStar071

Copy link
Copy Markdown
Member

🔗 Linked issue

Resolves #243

🧭 Context

When a READY no longer lists a cached guild, GatewayClient drops it from the cache and emits guildDelete, but it did so with a direct emit. It never emitted dispatch, which is the only event forwardGatewayDispatches listens to. A bot that runs its dispatch listeners only on @wolfstar/plugin-broker workers never saw these deletions, so it did not clean up the guilds it left while offline.

While looking at discordjs/discord.js#11547 (the same reconnect scenario), I checked whether plugin-gateway has the same problems. The main one, left guilds staying cached after a new READY, was already handled by dropUnlistedGuilds. The second part of that PR, tracking expectedGuilds per shard, does apply here and is fixed in its own commit.

📚 Description

Forwarding the dropped guild's delete (the issue). dropUnlistedGuilds now runs the made-up GUILD_DELETE through the same steps handleDispatch takes for a real one: the action's before (the cached guild, as the state), the cache write, dispatch, then the action's handle, which emits guildDelete. The broker therefore publishes it with the serialized guild state, and replayDispatch on the workers emits guildDelete with [Guild, { id }], the same arguments the gateway process's listeners get. raw is not emitted for it, since Discord never sent it, and it carries s: 0, which replayDispatch already reads as "unknown". plugin-broker needs no code change.

Tracking clientReady's expected guilds by shard (from discord.js#11547). #expectedGuilds was one Set that every shard's READY added to. As a result:

  • if a shard identified again before clientReady (its session was invalidated during startup) and the bot had left a guild in between, the stale ID kept clientReady waiting until waitGuildTimeout;
  • after clientReady, every reconnect's READY added IDs that nothing ever removed.

It is now a Map<shardId, Set>. A shard's READY replaces its own entry, and only before clientReady. Empty entries are removed, so the existing size/clear() checks in #checkClientReady keep their meaning.

How this compares to the Pullfrog plan: I followed it as written, including the action.before/action.handle refactor it suggested over the minimal emit version. The per-shard expectedGuilds commit is not in the plan. It comes from comparing against discord.js#11547, as requested.

Verification: after rebasing on main, pnpm lint, pnpm build, pnpm typecheck and pnpm test (121 files, 1712 tests) all pass. Each new test fails without its fix and passes with it.

Key changes

Area Before After
Guild dropped by READY only guildDelete, emitted directly dispatch (after the cache write, with the cached guild as state), then guildDelete through the GUILD_DELETE action
Broker workers never see these deletions the worker's guildDelete gets [Guild, { id }]
clientReady expected guilds one global set, only ever added to per shard; replaced by that shard's next READY; no longer collected after clientReady
  • packages/plugin-gateway/src/GatewayClient.ts: dropUnlistedGuilds, #expectedGuilds, new #forgetExpectedGuild.
  • packages/plugin-gateway/src/util/events.ts and both READMEs: document that dispatch covers the made-up GUILD_DELETE.
  • Tests: hardening.test.ts (dispatch payload, state, order, no raw), plugin-broker/tests/gateway-replay.test.ts (end to end through the fake Redis), client-ready.test.ts (two per-shard cases).
  • Two patch changesets: plugin-gateway + plugin-broker (README) for the forwarding, and plugin-gateway for the per-shard tracking.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation change
  • Workflow / CI change

Pre-flight Checklist

  • I have read the Contributing Guidelines (if applicable)
  • My code follows the code style of this project
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly
  • I added a changeset with pnpm changeset (or pnpm changeset add --empty if no release is needed). See .changeset/README.md.

🤖 AI disclosure: Claude Code was used on this PR. Code written with claude-opus-5-5; PR description written with claude-opus-5-5. AI policy.

dropUnlistedGuilds now takes the made-up GUILD_DELETE through the steps
handleDispatch takes for a real one: the action's before, the cache write,
dispatch, then the action's handle. forwardGatewayDispatches only listens to
dispatch, so broker workers never saw these deletions.

Refs #243
A shard's new READY replaced nothing in the global expected guilds set, so a
guild the bot left between two READYs kept clientReady waiting until
waitGuildTimeout, and READYs after clientReady filled a set never emptied.
Mirrors discordjs/discord.js#11547.

Refs #243
@coldtea-pr-lens

coldtea-pr-lens Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Architecture

Architecture diagram for wolfstar-project/plugins at 306aa95

Play the walkthrough


Inside the changed components — 2 views

Component view — Gateway reconciliation

Internal dispatch and action lifecycle when GatewayClient reconciles dropped guilds on READY.

Architecture view of Component view — Gateway reconciliation in wolfstar-project/plugins

Component view — Broker dispatch forwarding

Forwarding dropped guild dispatches across broker producers and consumers.

Architecture view of Component view — Broker dispatch forwarding in wolfstar-project/plugins

Data flow

Data flow diagram for wolfstar-project/plugins at 306aa95

Follow each request


Review

Nothing flagged · reviewed 306aa95


Tip

After a push, tick Redraw to draw the latest commit

🪧 More tips
  • Run npx skills add coldteadotai/pr-lens, then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Run npx @coldtea/pr-lens-cli analyze --base origin/main on a branch, then npx @coldtea/pr-lens-cli render .pr-lens/graph.json. Same lenses, your own model key, before the pull request exists
  • Click the link under each diagram to open it on a canvas you can zoom, pan and step through
  • The diagrams are links. Click one to open it on the canvas, then press W or click play to walk through the change
  • Open a diagram on the canvas, then press W or click play to walk through the change one step at a time
  • The CLI's render reads .github/pr-lens.yml and applies your renames, exclusions and lane pins at draw time
  • Set github.comment.collapsed: true in .github/pr-lens.yml to fold the comment behind one View architecture and data flow row. Drawing still runs as before
  • Set github.draw: on-demand in .github/pr-lens.yml and PR Lens stops drawing pull requests on its own. Comment @pr-lens draw on a pull request when you want that one drawn
  • Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and your model provider's key as its api-key to run PR Lens from your own CI. Any /chat/completions endpoint works
  • Switch GitHub to dark mode and the diagrams follow. The moving dots are this pull request's data in motion

Thanks for using PR Lens! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

@wolfstarbot wolfstarbot added packages:plugin-gateway Changes in plugin-gateway packages:plugin-broker Changes in plugin-broker labels Oct 17, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes: the full diff of both commits, checked against handleDispatch, the GuildDelete dispatch handler and its state codec, and #checkClientReady.

  • Forward the dropped guild's delete: dropUnlistedGuilds now takes the made-up GUILD_DELETE through before, the cache write, emit("dispatch") and handle, in the same order as handleDispatch. It skips raw, so forwardGatewayDispatches publishes it with the serialized guild state. The GuildDelete entry in DispatchStateCodecs already covers that state.
  • Per-shard expectedGuilds: #expectedGuilds becomes Map<number, Set<string>>. A shard's READY replaces its own entry only before clientReady, and empty entries are removed. The size and clear() checks in #checkClientReady therefore keep their meaning.
  • Docs, changesets and tests: events.ts and both READMEs are updated, and there are two patch changesets. Tests cover the dispatch payload, state, order and absence of raw, the end-to-end broker replay, and two per-shard clientReady cases.

Pullfrog  | View workflow run | Using claude-sonnet-5-5 | 𝕏

@kodiakhq
kodiakhq Bot merged commit 6012bd9 into main Oct 17, 2026
19 checks passed
@kodiakhq
kodiakhq Bot deleted the fix/plugin-gateway-forward-ready-guild-delete branch October 17, 2026 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

packages:plugin-broker Changes in plugin-broker packages:plugin-gateway Changes in plugin-gateway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plugin-gateway: the guildDelete of a guild a READY no longer lists never reaches the broker's workers

2 participants