[BUG] Discord plugin shows connected under --channels but does not receive gateway messages on Linux

Status Closed — not planned
Reported on v2.1.80
Maintainer reply None cached
Activity 15 comments · opened Mar 20, 2026 · closed Jun 25, 2026

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 pending stays empty, no messages delivered)
  • Standalone bun run start on 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):

  • /mcp shows 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

  1. Configure Discord bot token: /discord:configure <token>
  2. On Linux, run: claude --channels plugin:discord@claude-plugins-official
  3. Confirm plugin shows ✔ connected in /mcp
  4. DM the bot from Discord
  5. 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 to 162.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

View original on GitHub ↗

14 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/36825
  2. https://github.com/anthropics/claude-code/issues/36503
  3. https://github.com/anthropics/claude-code/issues/36836

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

tjvjbtwbnk-star · 5 months ago

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 with channel <id> is not allowlisted. Inbound messages sometimes still arrive (gateway partially alive). access.json is correct on disk.

Root cause analysis: The fetchAllowedChannel() function in server.ts (line ~392) calls client.channels.fetch(id) which returns a DMChannel object. It then checks ch.recipientId against access.allowFrom. When the gateway connection degrades, the channel fetch returns an incomplete object where recipientId is not properly resolved — failing the allowlist check even though the user IS in allowFrom.

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: /mcp reconnect from the Claude Code REPL restores functionality immediately.

Suggested fix:

  • Force-fetch the channel (bypass cache) when recipientId is falsy: client.channels.fetch(id, { force: true })
  • Or add gateway health monitoring with auto-reconnect on silent disconnection
jmgeorgh · 5 months ago

Additional Diagnostic Findings

Traced the intermittent "channel not allowlisted" failure to the exact code path in server.ts.

Root Cause

fetchAllowedChannel() (server.ts:403) calls client.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.recipientId is undefined, causing the access.allowFrom.includes(ch.recipientId) check (line 407) to fail — even though access.json is correct on disk.

Evidence

  • access.json always has the correct user ID in allowFrom during failures
  • Inbound messages (via gate()) still arrive because they use msg.author.id (from the live gateway event), not the cached channel
  • Outbound replies (fetchAllowedChannel()) fail because they depend on the cached channel's recipientId
  • The failure is consistently reproducible after 15-30+ minutes of no outbound Discord activity
  • Running /mcp to reconnect the plugin immediately fixes it (refreshes the cache)

Suggested Fix

// server.ts:392-393
async function fetchTextChannel(id: string) {
-  const ch = await client.channels.fetch(id)
+  const ch = await client.channels.fetch(id, { force: true })
   if (!ch || !ch.isTextBased()) {
     throw new Error(`channel ${id} not found or not text-based`)
   }
   return ch
}

Adding { force: true } forces a fresh API call instead of returning potentially stale cache. Since fetchTextChannel is 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

  • Claude Code v2.1.84 on WSL2 (Ubuntu)
  • discord.js ^14.14.0
  • Plugin version: discord@claude-plugins-official 0.0.4
  • Session uptime: 1-12+ hours, failure occurs after idle gaps
hawkins65 · 4 months ago

Additional findings: v2.1.97 Linux — MCP connects, gateway never establishes

Environment:

  • Claude Code v2.1.97 (latest as of 2026-04-09)
  • Ubuntu Linux 6.8.0-71-generic, x86_64
  • Bun 1.x installed at ~/.bun/bin/bun
  • Auth: claude.ai OAuth (Max plan)
  • Plugin: discord@claude-plugins-official v0.0.4

What works:

  • ✅ Bot token valid (verified via curl /users/@me)
  • ✅ Bot is in the guild (verified via /users/@me/guilds)
  • ✅ Plugin installed, enabled in settings
  • bun server.ts standalone — connects to Discord gateway, receives DMs
  • ✅ MCP server "discord" connects via stdio: Successfully connected (transport: stdio) in 208ms
  • ✅ Channel notifications registered: MCP server "discord": Channel notifications registered

What doesn't work:

  • ❌ Claude Code never spawns a persistent bun child process — pstree shows only {claude} threads, no bun subprocess
  • ❌ No Discord gateway TCP connection (no connections to 162.159.x.x)
  • ❌ DMs fail: "Your message could not be delivered"
  • /mcp shows discord · ✘ failed despite debug log saying "connected" and "registered"

Workaround attempt: Used --dangerously-load-development-channels server:discord with a manual .mcp.json entry (absolute path to bun server.ts). This successfully:

  1. Connected the MCP server via stdio
  2. Registered channel notifications (Channel notifications registered in 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):

MCP server "discord": Successfully connected (transport: stdio) in 208ms
MCP server "discord": Connection established with capabilities: {"hasTools":true,...}
MCP server "discord": Channel notifications registered

No stderr from the bun process. No gateway connection. No errors. The process simply exits silently after the stdio handshake.

Contrast with standalone (works):

cd ~/.claude/plugins/cache/claude-plugins-official/discord/0.0.4
echo '{}' | bun server.ts  # exits immediately (stdin closes → shutdown handler)
bun server.ts < /dev/zero  # stays alive, connects to Discord gateway

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.

nobonne · 4 months ago

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 with channel <id> is not allowlisted despite correct access.json. The block affects ALL MCP operations (both read and write), not just Discord API calls.

Key observations from our investigation:

  • server.ts has no timeout/keepalive/rate-limit logic — the error originates from fetchAllowedChannel() but the root cause is upstream
  • fetch_messages (read-only) fails with the same error, confirming it's not a Discord API rate limit
  • Rewriting access.json has no effect
  • The block is immediately lifted when a new inbound Discord message arrives (triggering an MCP notification)
  • Heavy internal processing (multiple image file reads, long grep operations, background rsync) correlates with triggering the block, but simple text exchanges can also trigger it after enough back-and-forth

Environment: 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)

IgorGanapolsky · 3 months ago

Replying to my own earlier comment with a correction — I claimed --verbose log lines named channel_event / user_turn would 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 start receives DMs and emits notifications/claude/channel JSON-RPC frames; same token works on macOS claude --channels immediately; Linux claude --channels reports connected in /mcp but access.json pending stays 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 /mcp output + access.json and 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.

DeliLevente99 · 3 months ago

Cross-linking #36431 — the Telegram plugin reproduces the same notifications/claude/channel silent-drop on macOS. @keitotatsuguchi confirmed there via file-level logging in server.ts that mcp.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.

DeliLevente99 · 3 months ago

New data point that isolates this further. On the same Claude Code instance (Kali Linux 2025.x on WSL2) with:

  • tengu_harbor: true cached in ~/.claude.json
  • official discord entry present in tengu_harbor_ledger
  • no DISABLE_TELEMETRY / CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC set anywhere (env, shell profiles, /etc/environment, settings.json)
  • launched with --channels plugin:discord@claude-plugins-official

discord@claude-plugins-official does 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-channel plugin (also an MCP server, also using notifications/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/channel handler failure: the same notification path works for a custom-marketplace MCP plugin but fails for claude-plugins-official plugins 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.

DeliLevente99 · 3 months ago

Refinement to my earlier marketplace-isolation comment. On the same Kali Linux WSL2 setup, telegram@claude-plugins-official v0.0.6 (same claude-plugins-official marketplace 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).

DeliLevente99 · 3 months ago

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-official

Result:

  • Discord MCP server connects ✓ — outbound reply works (the bot posted an auto-greeting in the configured guild channel from inside the session)
  • Inbound from Discord: bot shows typing indicator on every received message (DM, plain channel msg, channel msg with explicit @mention) — confirms handleInbound runs and mcp.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).
  • Inbound from Telegram on the same session: works perfectly.

Verified the Kali cache server.ts already contains the April 14 dmChannelUsers fallback fix (#1365) at server.ts:409, so this is NOT the recipientId == null cache-staleness bug — it''s the inbound notification delivery path that''s silently dropping for Discord specifically.

This cleanly isolates the bug:

  • NOT marketplace-wide (both plugins are claude-plugins-official, Telegram delivers inbound fine)
  • NOT general client handler failure (Slack-channel + Telegram both deliver via the same notifications/claude/channel path on the same session)
  • NOT the recipientId fix (already present in the cached server.ts)
  • IS Discord-plugin-specific or Discord-routing-specific in the client''s notification handling

Retracts my earlier marketplace-isolation framing above — this is sharper and lab-reproducible.

DeliLevente99 · 3 months ago

Solution confirmed working on the same Kali Linux WSL2 setup from my earlier comment. Use --dangerously-load-development-channels instead of --channels:

| Channel | --channels | --dangerously-load-development-channels |
|----------|------------------|------------------------------------------|
| Telegram | works | works |
| Slack | works | works |
| Discord | blocked silently | works |

Under --channels plugin:discord@claude-plugins-official the 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 --channels allowlist check, not to a host-side notification handler gap. The per-channel allowlist appears to be independent of the tengu_harbor_ledger cache (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.)_

DeliLevente99 · 3 months ago

Direct verification on the same setup (was extrapolated earlier).

Launched a separate session with the workaround flag:

claude --dangerously-load-development-channels plugin:discord@claude-plugins-official \
       "...respond with PONG-FROM-DEV-FLAG..."

Result: inbound delivered (a ? discord ? <user>: <text> line surfaces in the session), and the assistant invoked the reply tool to send PONG-FROM-DEV-FLAG back to Discord ? confirming the dev-flag launch path end-to-end, not just polling/fallback. The -DEV-FLAG suffix 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:

  • Client-side "approved channels list" check ? bypassed by --dangerously-load-development-channels. This is the actual fix in this thread.
  • Plugin-side access.json allowlist (dmPolicy, allowFrom, groups) ? independent, gates only the reply tool, 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 deliver notifications/claude/channel correctly. The --bg daemon-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-channels instead ? 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.

github-actions[bot] · 2 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

Showing cached comments. Read the full discussion on GitHub ↗