From f7a8c2df2c6eea297f2f5e702f23f0ba2fa574d6 Mon Sep 17 00:00:00 2001 From: Shadow Date: Thu, 19 Feb 2026 13:47:28 -0600 Subject: [PATCH] Discord: handle gateway 4014 close --- CHANGELOG.md | 1 + src/discord/monitor/provider.ts | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb4c092881..45c636d091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow. - Security/Net: strip sensitive headers (`Authorization`, `Proxy-Authorization`, `Cookie`, `Cookie2`) on cross-origin redirects in `fetchWithSsrFGuard` to prevent credential forwarding across origin boundaries. (#20313) Thanks @afurm. - Auto-reply/Runner: emit `onAgentRunStart` only after agent lifecycle or tool activity begins (and only once per run), so fallback preflight errors no longer mark runs as started. (#21165) Thanks @shakkernerd. - Auto-reply/Prompt caching: restore prefix-cache stability by keeping inbound system metadata session-stable and moving per-message IDs (`message_id`, `message_id_full`, `reply_to_id`, `sender_id`) into untrusted conversation context. (#20597) Thanks @anisoptera. diff --git a/src/discord/monitor/provider.ts b/src/discord/monitor/provider.ts index c7c54ec909..7dbe4195ff 100644 --- a/src/discord/monitor/provider.ts +++ b/src/discord/monitor/provider.ts @@ -5,7 +5,7 @@ import { type BaseMessageInteractiveComponent, type Modal, } from "@buape/carbon"; -import type { GatewayPlugin } from "@buape/carbon/gateway"; +import { GatewayCloseCodes, type GatewayPlugin } from "@buape/carbon/gateway"; import { Routes } from "discord-api-types/v10"; import { resolveTextChunkLimit } from "../../auto-reply/chunk.js"; import { listNativeCommandSpecsForConfig } from "../../auto-reply/commands-registry.js"; @@ -167,6 +167,16 @@ function formatDiscordDeployErrorDetails(err: unknown): string { return details.length > 0 ? ` (${details.join(", ")})` : ""; } +const DISCORD_DISALLOWED_INTENTS_CODE = GatewayCloseCodes.DisallowedIntents; + +function isDiscordDisallowedIntentsError(err: unknown): boolean { + if (!err) { + return false; + } + const message = formatErrorMessage(err); + return message.includes(String(DISCORD_DISALLOWED_INTENTS_CODE)); +} + export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { const cfg = opts.config ?? loadConfig(); const account = resolveDiscordAccount({ @@ -643,6 +653,8 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { }, HELLO_TIMEOUT_MS); }; gatewayEmitter?.on("debug", onGatewayDebug); + // Disallowed intents (4014) should stop the provider without crashing the gateway. + let sawDisallowedIntents = false; try { await waitForDiscordGatewayStop({ gateway: gateway @@ -653,15 +665,30 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { : undefined, abortSignal, onGatewayError: (err) => { + if (isDiscordDisallowedIntentsError(err)) { + sawDisallowedIntents = true; + runtime.error?.( + danger( + "discord: gateway closed with code 4014 (missing privileged gateway intents). Enable the required intents in the Discord Developer Portal or disable them in config.", + ), + ); + return; + } runtime.error?.(danger(`discord gateway error: ${String(err)}`)); }, shouldStopOnError: (err) => { const message = String(err); return ( - message.includes("Max reconnect attempts") || message.includes("Fatal Gateway error") + message.includes("Max reconnect attempts") || + message.includes("Fatal Gateway error") || + isDiscordDisallowedIntentsError(err) ); }, }); + } catch (err) { + if (!sawDisallowedIntents && !isDiscordDisallowedIntentsError(err)) { + throw err; + } } finally { unregisterGateway(account.accountId); stopGatewayLogging();