[BUG] Discord plugin shows connected under --channels but does not receive gateway messages on Linux
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When running claude --channels plugin:discord@claude-plugins-official on Linux, the Discord plugin reports ✔ connected in /mcp, but the bot never receives Discord DMs. No pairing codes are issued, and no messages are delivered to the Claude session.
The same bot token works immediately on macOS. Running the plugin's bun server standalone on the same Linux machine also works correctly — DMs are received and MCP notifications are generated.
Evidence:
- Plugin status in
/mcp:✔ connected✅ - Discord gateway TCP connection established (ESTAB to 162.159.x.x) ✅
- DMs received by bot: ❌ (access.json
pendingstays empty, no messages delivered) - Standalone
bun run starton same machine: ✅ (DMs received, MCP notifications output to stdout) - Same token on macOS
claude --channels: ✅ (works immediately)
Standalone test output (works):
discord channel: gateway connected as ControlClaude#9927
{"method":"notifications/claude/channel","params":{"content":"testing","meta":{"chat_id":"...","message_id":"...","user":"keezer","user_id":"713558987664130068","ts":"2026-03-20T21:54:33.645Z"}},"jsonrpc":"2.0"}
Via claude --channels (broken):
/mcpshows plugin as✔ connected- Sending DMs produces no output, no access.json changes, no Claude response
What Should Happen?
DMs sent to the bot should be received and delivered to the Claude session, same as on macOS.
Steps to Reproduce
- Configure Discord bot token:
/discord:configure <token> - On Linux, run:
claude --channels plugin:discord@claude-plugins-official - Confirm plugin shows
✔ connectedin/mcp - DM the bot from Discord
- Observe: no response, no pairing code, access.json unchanged
Contrast with working standalone:
cd ~/.claude/plugins/cache/claude-plugins-official/discord/0.0.1
source ~/.claude/channels/discord/.env
DISCORD_BOT_TOKEN=$DISCORD_BOT_TOKEN bun run start 2>&1
# → "discord channel: gateway connected as BotName#XXXX"
# → DMs received immediately
Hypothesis
The Discord plugin's MCP transport connects successfully (explaining the ✔ connected status), but the Discord.js WebSocket gateway session is not functioning when the plugin is spawned as a subprocess by claude --channels. The TCP connection to Discord's Cloudflare IPs appears established but messages are not delivered — possibly the gateway session is authenticating but not properly receiving dispatch events in this subprocess context.
Error Messages/Logs
No errors visible. The plugin shows as connected and produces no stderr output. The Discord gateway TCP connection shows as ESTAB but no messages arrive.
Claude Code Version
2.1.80
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux (kernel 6.8.0-100-generic)
Terminal/Shell
bash
Additional Information
- macOS with same token and same plugin version works immediately
- The bot process spawned by
claude --channels(bun subprocess) has an ESTAB TCP connection to162.159.130.234:443(Discord/Cloudflare), suggesting the gateway connection is established at the TCP level but not functioning at the application layer - No competing instances running during testing
Showing cached comments. Read the full discussion on GitHub ↗
14 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
See if this helps
https://github.com/anthropics/claude-code/issues/36503#issuecomment-4102449083
Adding findings from a related issue (#37748) that may help diagnose this:
Environment: Ubuntu Linux, headless tmux session, bun 1.3.11, discord.js 14.25.1, plugin v0.0.1
Symptom: After extended idle periods, outbound tools (
reply,fetch_messages) fail withchannel <id> is not allowlisted. Inbound messages sometimes still arrive (gateway partially alive).access.jsonis correct on disk.Root cause analysis: The
fetchAllowedChannel()function inserver.ts(line ~392) callsclient.channels.fetch(id)which returns aDMChannelobject. It then checksch.recipientIdagainstaccess.allowFrom. When the gateway connection degrades, the channel fetch returns an incomplete object whererecipientIdis not properly resolved — failing the allowlist check even though the user IS inallowFrom.The access file is re-read from disk on every call (not cached), so the issue is specifically the Discord.js gateway/channel cache becoming stale, not an access config problem.
Workaround:
/mcpreconnect from the Claude Code REPL restores functionality immediately.Suggested fix:
recipientIdis falsy:client.channels.fetch(id, { force: true })Additional Diagnostic Findings
Traced the intermittent "channel not allowlisted" failure to the exact code path in
server.ts.Root Cause
fetchAllowedChannel()(server.ts:403) callsclient.channels.fetch(id)without{ force: true }. After idle periods (~15-30 min), the Discord.js client cache evicts or partials the DMChannel object. When the cached partial is returned,ch.recipientIdisundefined, causing theaccess.allowFrom.includes(ch.recipientId)check (line 407) to fail — even thoughaccess.jsonis correct on disk.Evidence
access.jsonalways has the correct user ID inallowFromduring failuresgate()) still arrive because they usemsg.author.id(from the live gateway event), not the cached channelfetchAllowedChannel()) fail because they depend on the cached channel'srecipientId/mcpto reconnect the plugin immediately fixes it (refreshes the cache)Suggested Fix
Adding
{ force: true }forces a fresh API call instead of returning potentially stale cache. SincefetchTextChannelis only called for outbound operations (replies, fetching messages), the additional API call is acceptable — it happens at most once per user interaction, not in a hot loop.Environment
Additional findings: v2.1.97 Linux — MCP connects, gateway never establishes
Environment:
~/.bun/bin/bundiscord@claude-plugins-officialv0.0.4What works:
curl /users/@me)/users/@me/guilds)bun server.tsstandalone — connects to Discord gateway, receives DMsSuccessfully connected (transport: stdio) in 208msMCP server "discord": Channel notifications registeredWhat doesn't work:
pstreeshows only{claude}threads, no bun subprocess162.159.x.x)/mcpshowsdiscord · ✘ faileddespite debug log saying "connected" and "registered"Workaround attempt: Used
--dangerously-load-development-channels server:discordwith a manual.mcp.jsonentry (absolute path tobun server.ts). This successfully:Channel notifications registeredin debug log)But the bun process exits after the MCP handshake — it is not kept alive as a persistent child. The Discord.js gateway session is never established because the process doesn't survive long enough to call
client.login().Debug log evidence (session
8692da6a):No stderr from the bun process. No gateway connection. No errors. The process simply exits silently after the stdio handshake.
Contrast with standalone (works):
The server shuts down on stdin EOF (line 732:
process.stdin.on('end', shutdown)). If Claude Code's stdio transport doesn't keep stdin open, or closes it after the MCP handshake, the server self-terminates before the Discord gateway can connect.Hypothesis: Claude Code completes the MCP capability negotiation over stdio, then either closes stdin or stops reading, triggering the server's
stdin.on('end')shutdown handler. The server needs stdin to remain open for the lifetime of the session to maintain the Discord gateway.Adding another data point from a macOS (Apple Silicon, Darwin 25.4.0) environment with Discord plugin v0.0.4.
Same core symptom:
reply,fetch_messages, and all other MCP tool calls intermittently fail withchannel <id> is not allowlisteddespite correctaccess.json. The block affects ALL MCP operations (both read and write), not just Discord API calls.Key observations from our investigation:
server.tshas no timeout/keepalive/rate-limit logic — the error originates fromfetchAllowedChannel()but the root cause is upstreamfetch_messages(read-only) fails with the same error, confirming it's not a Discord API rate limitaccess.jsonhas no effectEnvironment: Claude Code (latest,
--channels plugin:discord@claude-plugins-official), macOS, running in tmux via LaunchAgent with 5-minute auto-restart.Related: #47680 (our original report, closing as duplicate of this issue)
Replying to my own earlier comment with a correction — I claimed
--verboselog lines namedchannel_event/user_turnwould surface this. I don't have evidence those specific labels exist; that part was inferred and may be wrong. The grounded version of the same diagnosis using only what your bug report and other linked issues confirm:Your evidence in the bug report (standalone
bun run startreceives DMs and emitsnotifications/claude/channelJSON-RPC frames; same token works on macOSclaude --channelsimmediately; Linuxclaude --channelsreportsconnectedin/mcpbutaccess.jsonpendingstays empty) already isolates this to the Claude-Code-side consumption of plugin notifications, not the plugin's Discord gateway. That's the same pattern across #36657 and #37287.Wrote up the seven Channels-plugin friction points (Message Content Intent, --channels flag, pairing order, shared-server requirement, session-resume detach, the notification consumption gap you're hitting, permission-relay): https://igorganapolsky.github.io/openclaw-mac-ai-workstation-setup/claude-code-channels-not-working.html
If you want me to read your
/mcpoutput +access.jsonand confirm which of the seven matches yours (vs. a different cause), $19 quick read: https://igorganapolsky.github.io/openclaw-mac-ai-workstation-setup/speed-to-lead.html — refund if I can't name a likely cause.Cross-linking #36431 — the Telegram plugin reproduces the same
notifications/claude/channelsilent-drop on macOS. @keitotatsuguchi confirmed there via file-level logging inserver.tsthatmcp.notification(...)resolves successfully (the Promise completes without error) but the event never surfaces in the conversation. That narrows this specific symptom to the Claude Code client''s notification handler, not the plugin''s send path — adding it here as a data point.New data point that isolates this further. On the same Claude Code instance (Kali Linux 2025.x on WSL2) with:
tengu_harbor: truecached in~/.claude.jsondiscordentry present intengu_harbor_ledgerDISABLE_TELEMETRY/CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFICset anywhere (env, shell profiles,/etc/environment, settings.json)--channels plugin:discord@claude-plugins-official→
discord@claude-plugins-officialdoes NOT deliver inbound DMs to the conversation (matches every report in this thread).Meanwhile, on the exact same Claude Code session, the community
slack-channel@claude-code-slack-channelplugin (also an MCP server, also usingnotifications/claude/channel, but published in a separate custom marketplace) DOES deliver inbound DMs end-to-end — verified with a live exchange.This rules out a general client-side
notifications/claude/channelhandler failure: the same notification path works for a custom-marketplace MCP plugin but fails forclaude-plugins-officialplugins on identical settings. The bug appears isolated to the official-marketplace registration path — consistent with Gate 6 (plugin marketplace source verification) in @soumikbhatta''s breakdown in #36460 rather than Gate 2 (tengu_harbor) or the handler itself.The ledger entry being present is therefore not sufficient — something downstream of the ledger check still rejects official-marketplace plugins on affected accounts while custom-marketplace plugins go through.
Refinement to my earlier marketplace-isolation comment. On the same Kali Linux WSL2 setup,
telegram@claude-plugins-officialv0.0.6 (sameclaude-plugins-officialmarketplace as Discord) DOES deliver inbound messages end-to-end. So the bug is NOT marketplace-wide as I framed it — the Slack-channel vs Discord asymmetry I observed is more likely Discord-plugin-specific (possibly stuck on v0.0.4 while Telegram has had 0.0.2 → 0.0.6 releases that may have addressed similar issues).Suggested check for affected users here: see whether a newer Discord plugin release is available — if not, that itself may be the root cause (release cadence rather than handler/gate logic).
Live reproduction on the same Kali Linux WSL2 setup where Telegram + a community slack-channel plugin both deliver inbound end-to-end.
Same Claude Code session, launched with both:
--channels plugin:telegram@claude-plugins-official--channels plugin:discord@claude-plugins-officialResult:
replyworks (the bot posted an auto-greeting in the configured guild channel from inside the session)@mention) — confirmshandleInboundruns andmcp.notification(notifications/claude/channel, ...)fires — but the message never surfaces in the conversation. All three input variants produce the same outcome (typing → silence → no reply).Verified the Kali cache
server.tsalready contains the April 14dmChannelUsersfallback fix (#1365) atserver.ts:409, so this is NOT therecipientId == nullcache-staleness bug — it''s the inbound notification delivery path that''s silently dropping for Discord specifically.This cleanly isolates the bug:
claude-plugins-official, Telegram delivers inbound fine)notifications/claude/channelpath on the same session)recipientIdfix (already present in the cachedserver.ts)Retracts my earlier marketplace-isolation framing above — this is sharper and lab-reproducible.
Solution confirmed working on the same Kali Linux WSL2 setup from my earlier comment. Use
--dangerously-load-development-channelsinstead of--channels:| Channel | --channels | --dangerously-load-development-channels |
|----------|------------------|------------------------------------------|
| Telegram | works | works |
| Slack | works | works |
| Discord | blocked silently | works |
Under
--channels plugin:discord@claude-plugins-officialthe Discord plugin is not on the org's approved channels list, so the channel notification handler is silently not registered — exactly the symptom this thread describes. Switching to:claude --dangerously-load-development-channels plugin:discord@claude-plugins-official
bypasses the allowlist check and inbound DMs surface in the conversation immediately on the same account, same session.
This corroborates @hibbes's 2026-04-23 follow-up in the now-closed #36975 and @tommohide-kumekawa's macOS confirmation there — the "notifications never surface" symptom traces back to the
--channelsallowlist check, not to a host-side notification handler gap. The per-channel allowlist appears to be independent of thetengu_harbor_ledgercache (which can contain the official Discord entry while the runtime check still rejects it).For affected users: try the dev flag form above. Telegram and Slack work both ways on the same account where Discord is blocked — Discord is the plugin where the difference shows up.
_(Edit: previous version of this comment had its table emojis mangled by a PowerShell stdin encoding issue; switched to plain text for the status column.)_
Direct verification on the same setup (was extrapolated earlier).
Launched a separate session with the workaround flag:
Result: inbound delivered (a
? discord ? <user>: <text>line surfaces in the session), and the assistant invoked thereplytool to sendPONG-FROM-DEV-FLAGback to Discord ? confirming the dev-flag launch path end-to-end, not just polling/fallback. The-DEV-FLAGsuffix is the deliberate marker: it shows the dev-flag route is what fired, not some other delivery path.Two independent gates seen on the same account, worth keeping separate when diagnosing:
--dangerously-load-development-channels. This is the actual fix in this thread.access.jsonallowlist (dmPolicy,allowFrom,groups) ? independent, gates only thereplytool, leaves inbound visible. Confirmed: an inbound from a non-allowlisted DM channel still surfaces in the conversation; only the reply call is then refused with a "channel not allowlisted" notice.Cross-ref to @oskarmodig's recent comment on #36431: this reproduction is in a tmux session (not
--bg), consistent with their observation that interactive/tmux sessions do delivernotifications/claude/channelcorrectly. The--bgdaemon-drop they report looks like a distinct path ? both feed into the same symptom from different angles.Tip for users running a Claude Code session already: you can simply ask Claude to patch your channel-launcher (whatever script / agent process invokes
claude --channels ?) to use--dangerously-load-development-channelsinstead ? that's what worked here while an upstream fix lands. It's a single-flag change in the spawn command; Claude can find and apply it across a repo in seconds.Closing for now — inactive for too long. Please open a new issue if this is still relevant.