Summary
Since the switch to @buape/carbon as the Discord client library (present in at least 2026.3.22+), the Discord gateway WebSocket frequently fails to connect on startup. The bot logs client initialized as ... ; awaiting gateway readiness but never reaches logged in to discord.
Root Cause
In @buape/carbon's Client constructor (node_modules/@buape/carbon/dist/src/classes/Client.js), plugins are registered synchronously:
for (const plugin of plugins) {
plugin.registerClient?.(this); // NOT awaited
plugin.registerRoutes?.(this);
this.plugins.push({ id: plugin.id, plugin });
}
However, SafeGatewayPlugin.registerClient (in openclaw's provider code) is async — it fetches gateway metadata via fetchDiscordGatewayInfoWithTimeout() before calling super.registerClient(client) → this.connect(). Because the promise is fire-and-forget, the gateway connection starts asynchronously with no guaranteed completion before the rest of the startup flow proceeds.
In practice, the async registration often completes too late or its result is silently lost, leaving lifecycleGateway.isConnected === false permanently.
Reproduction
- Install openclaw 2026.3.23 or 2026.3.24
- Start the gateway with a valid Discord bot token
- Observe logs:
client initialized as ... ; awaiting gateway readiness appears but logged in to discord never follows
lsof confirms no outbound WebSocket connection to Discord gateway servers
Workaround
Added a wait loop in the provider after deployDiscordCommands that polls lifecycleGateway.isConnected || lifecycleGateway.isConnecting for up to 15 seconds, then triggers an explicit gateway.connect() if needed:
if (lifecycleGateway && !lifecycleGateway.isConnected) {
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 500));
if (lifecycleGateway.isConnected || lifecycleGateway.isConnecting) break;
}
if (!lifecycleGateway.isConnected && !lifecycleGateway.isConnecting) {
if (!lifecycleGateway.gatewayInfo) {
const resp = await fetch("https://discord.com/api/v10/gateway/bot",
{ headers: { Authorization: `Bot ${token}` } });
lifecycleGateway.gatewayInfo = await resp.json();
}
lifecycleGateway.connect();
}
}
Suggested Fix
The cleanest fix would be to ensure the gateway plugin's async registration completes before proceeding. Options:
- In openclaw's provider:
await the gateway plugin's registerClient after constructing the Client, e.g. await client.getPlugin("gateway")?.registerClient?.(client) or store and await the promise.
- In @buape/carbon's Client: change the constructor to store plugin registration promises and expose an
async init() method, or make registerClient synchronous by deferring the gateway info fetch.
Environment
- openclaw 2026.3.23 / 2026.3.24
- Node.js v25.8.1
- macOS (Apple Silicon)
- @buape/carbon (bundled in openclaw)
Summary
Since the switch to
@buape/carbonas the Discord client library (present in at least 2026.3.22+), the Discord gateway WebSocket frequently fails to connect on startup. The bot logsclient initialized as ... ; awaiting gateway readinessbut never reacheslogged in to discord.Root Cause
In
@buape/carbon'sClientconstructor (node_modules/@buape/carbon/dist/src/classes/Client.js), plugins are registered synchronously:However,
SafeGatewayPlugin.registerClient(in openclaw's provider code) isasync— it fetches gateway metadata viafetchDiscordGatewayInfoWithTimeout()before callingsuper.registerClient(client)→this.connect(). Because the promise is fire-and-forget, the gateway connection starts asynchronously with no guaranteed completion before the rest of the startup flow proceeds.In practice, the async registration often completes too late or its result is silently lost, leaving
lifecycleGateway.isConnected === falsepermanently.Reproduction
client initialized as ... ; awaiting gateway readinessappears butlogged in to discordnever followslsofconfirms no outbound WebSocket connection to Discord gateway serversWorkaround
Added a wait loop in the provider after
deployDiscordCommandsthat pollslifecycleGateway.isConnected || lifecycleGateway.isConnectingfor up to 15 seconds, then triggers an explicitgateway.connect()if needed:Suggested Fix
The cleanest fix would be to ensure the gateway plugin's async registration completes before proceeding. Options:
awaitthe gateway plugin'sregisterClientafter constructing theClient, e.g.await client.getPlugin("gateway")?.registerClient?.(client)or store and await the promise.async init()method, or makeregisterClientsynchronous by deferring the gateway info fetch.Environment