Discord channel plugin: inbound notifications silently dropped on CC 2.1.278 (typing fires, no session turn) — resurfacing #86566
Summary
Inbound notifications from the discord@claude-plugins-official@0.0.4 plugin are silently dropped by Claude Code. The plugin's MCP server is connected and receives messages (Discord's typing indicator fires, proving the message reached the plugin and passed its access gate), but no <channel source="discord"> conversation turn is ever surfaced in the session and the session JSONL is not appended. Only the inbound notifications/claude/channel path is affected — outbound MCP tools (reply, fetch_messages, react) work fine when called from Claude.
Filing a fresh issue per the stalebot's request; #86566 covers the same symptom and was auto-closed on 2026-09-12 with "please open a new issue if this is still relevant." Related historical: #37287, #38104, #44283.
Environment
- Claude Code 2.1.278
- Plugin:
discord@claude-plugins-official@0.0.4(latest —claude plugin updateconfirms no newer version) - OS: Windows 11 Pro 26200 (PowerShell 5.1)
- Runtime: Bun (latest)
- Plan: Personal Pro
Repro
- Install
discord@claude-plugins-official, configure token via/discord:configure. - Pair a user via
/discord:access, confirmdmPolicy: allowlistwith the paired user inallowFrom. - Start a fresh
claudesession in any directory. - Confirm
/mcpshowsdiscordconnected. - From the paired Discord user, DM the bot.
Expected: Message appears in the session as a <channel source="discord" chat_id="..." ...> turn.
Actual: Discord shows typing indicator briefly (bot processed the DM and called sendTyping). No turn appears in Claude. ~/.claude/projects/**/*.jsonl for the session is never appended for this event.
What I verified
- Not a token race: only one
bun.exe server.tsprocess running for this plugin (confirmed viaGet-CimInstance Win32_Process). - Not a wrong-parent race: walked the process tree — the plugin's stdio parent (bun child → bun runner → claude.exe) is exactly the
claude.exePID hosting the interactive session. - Not a mid-session reload artifact: reproduces after full session restart (fresh
claude) and afterclaude --continue, not just after/reload-plugins. - Not a gate drop: typing indicator only fires after
gate()returnsdeliver(see pluginserver.tsL852–854), so the message passed the allowlist. - Not a
.envencoding issue: verified.envstarts with ASCIIDIS…, ends with\n, no BOM. - Outbound MCP fully works: from the CLI I can successfully call
mcp__plugin_discord_discord__fetch_messagesandmcp__plugin_discord_discord__replyagainst the same DM channel; only the push notification direction is broken.
Workaround in use
Polling fetch_messages every 30–60s from a /loop skill and calling reply for anything new. Functional but eats tokens on every idle tick and adds latency equal to the poll interval.
Ask
Happy to attach a redacted session JSONL, plugin stderr, or run a targeted diagnostic. The bug hits every attempt to use the official Discord channel plugin on 2.1.278 for me, so it's easy to reproduce on demand.
Showing cached comments. Read the full discussion on GitHub ↗
7 Comments
To prevent this issue from getting stale, you would need to comment every time before the 14 days interval expires. See https://github.com/anthropics/claude-code/issues/87647 for more details and consider upvoting that for visibility.
Hi @immefrank — I dug into the older reports because your repro looks like the notification is being lost after the plugin rather than at the Discord transport/access layer.
There is a particularly useful historical repro in #59240: notifications/claude/channel was confirmed working on Claude Code 2.1.114 and broken on every tested version from 2.1.119 through 2.1.142, using the official iMessage channel plugin. The reporter's temporary workaround was 2.1.114.
Since you're seeing the same failure pattern on 2.1.278, would you be willing to test 2.1.114 with the same Discord plugin/configuration?
If inbound Discord turns work there, that would give us a strong cross-plugin/cross-platform regression boundary rather than another Discord-specific configuration issue.
If you can test it, I'd also compare one inbound DM on 2.1.114 vs 2.1.278 with MCP/channel debug logging enabled. That may show exactly where the notification stops being dispatched.
Your current polling workaround shouldn't be necessary if this regression boundary still holds.
Ok, let me try and get back to you.
@hgelectricidad35-cloud thanks for the pointer to #59240.
I ran the sandboxed test: installed
@anthropic-ai/claude-code@2.1.114into an isolated directory, launched it withDISABLE_AUTOUPDATER=1(verified withclaude doctor), same~/.claude/config and samediscord@claude-plugins-official@0.0.4plugin.Result: same failure pattern on 2.1.114.
/mcpshowsplugin:discord:discord ✓ connected(single plugin server, verified only onebun.exe server.tsin the process tree and its parent chain leads to the 2.1.114claude.exe).sendTypingand MCP notification both dispatched).<channel source="discord">turn appears in the CLI. Session JSONL is not appended.So the 2.1.114-vs-later regression boundary from #59240 (iMessage) does not hold for the Discord channel plugin — Discord's inbound path was already broken at 2.1.114 in my setup.
Possibilities from here:
discord@claude-plugins-official@0.0.4(which was published after 2.1.114, in May 2026) — a manifest field or capability declaration Claude Code didn't yet honor at 2.1.114.Happy to test any specific older Discord plugin version, run with
ANTHROPIC_LOG=debug(or whichever debug flag is preferred), or attach redacted MCP logs from both versions if that helps narrow it down.Small unrelated observation from the debug: repeated plugin restart cycles quickly burn through Discord's ~1000/day gateway identify quota — I hit it doing plain reload testing yesterday. If the plugin can avoid a full re-identify on
/reload-plugins(e.g. persist a resume gateway URL/session_id) that'd help iteration under this bug.Thanks for running the 2.1.114 test — that rules out the regression
boundary I was considering.
I dug further after your result and found older reports showing essentially
the same inbound failure with Discord plugin v0.0.1 / Claude Code 2.1.80,
and a similar "notifications/claude/channel" failure reported with Telegram
as well.
That makes me think testing more Discord plugin versions may have
diminishing value. The stronger common denominator now looks like the
Claude Code handling/routing of "notifications/claude/channel", rather than
Discord 0.0.4 specifically.
If you're willing to run one more targeted test, I think the most useful
evidence would be debug/MCP logs around a single inbound DM, specifically
the point where the plugin emits "notifications/claude/channel" and what
Claude Code does immediately afterward. Redacted logs are absolutely fine.
Also, good catch on the Discord identify quota. I wouldn't burn more reload
cycles than necessary while testing this.
El jue, 24 de sept de 2026, 00:24, immefrank @.***>
escribió:
Wire-level MCP trace captured on 2.1.114 with
discord@0.0.4— I ran the plugin through a stdio tee so we can see both directions. Here are the relevant frames around a single inbound DM. IDs are the real ones from the traffic; content is a test string I sent.Setup:
.mcp.jsoncommand pointed at a Node script that forks the plugin'sbun ... startand logs stdin/stdout of both sides.1. Initialize (CC → plugin)
Client capabilities are
rootsandelicitationonly. Noexperimental.claude/channel(orexperimental.claude/channel/permission) declared by the client.2. Initialize response (plugin → CC)
Server advertises the experimental capabilities.
3. Gateway connect (plugin stderr)
4. Inbound DM notification (plugin → CC) — ~20s later
5. Claude Code's response
Nothing. The tee shows no bytes at all from CC → plugin after this notification. No ack, no error frame, no
notifications/*in return. No<channel source="discord">turn appears in the CLI and the session JSONL is not appended.---
Suggested root cause
Per the MCP spec, an experimental capability is expected to be declared on both sides of the initialize handshake before it can be used. Here the server declares
experimental.claude/channelbut the client (CC 2.1.114) does not, so the notification is effectively unsolicited from the client's perspective — which matches the silent-drop behaviour we see.However,
stringson the CC 2.1.114 binary does shownotifications/claude/channel,notifications/claude/channel/permission, andnotifications/claude/channel/permission_requestembedded (7 occurrences of the base method in the 2.1.278 binary as well, so it's not version-specific). So the handler code appears to exist but is dead code because the corresponding capability isn't advertised in the clientinitialize— meaning the server's negotiation path never enables the dispatch.If that reading is right, the fix is one of:
experimental.claude/channel(and.../permission) in itsinitializeclientCapabilitieswhenever the discord/telegram/imessage channel plugins are loaded (or unconditionally, if the handler is universally present).The plugin itself is well-behaved — it declares the capability, emits a well-formed JSON-RPC frame with proper content and metadata, and the DM's
contentandmetafields look correct. So this looks squarely like a CC-side handshake bug, consistent with @hgelectricidad35-cloud's read that the issue is CC's routing ofnotifications/claude/channelrather than anything specific to the Discord plugin.If a full un-redacted trace or the tee wrapper itself would be useful, happy to share.
Closing this — turned out to be operator error on my end, not a Claude Code bug.
Root cause: launching
claudewithout the--channels plugin:discord@claude-plugins-officialflag. Per the Channels docs, the flag is required every session; having the plugin in.mcp.jsonalone doesn't opt the session into the channel. Our wire trace made this visible in a sharper way than the docs warning did: the client'sinitializeframe advertises only{roots, elicitation}— noexperimental.claude/channel— so the server-emittednotifications/claude/channelis unsolicited from the client's perspective and silently dropped, even though CC contains handlers for it (the handler just isn't wired up in a session that didn't declare the capability).Confirmed fix (my install, Windows 11, CC 2.1.114 sandbox + CC 2.1.278):
With that flag, the exact same plugin + config that produced the drop now delivers the
<channel source="discord">turn immediately.Thanks @hgelectricidad35-cloud for the debugging guidance — the plugin-vs-handshake distinction and the ask for a wire trace were what led straight to the answer.
Suggestion for the docs / DX so the next person doesn't spend a day on this: the current failure mode is a completely silent drop. If CC printed a one-line warning at session start when a channel plugin is loaded but not activated via
--channels— something likediscord plugin loaded but --channels not specified; inbound messages will be ignored— this would be self-diagnosing. Happy to file that as a separate small enhancement request if useful.(Minor unrelated plugin-repo note that came up: on my Windows box the plugin's outbound
replyseems to see the same DM channel as a non-DM type when re-fetched viaclient.channels.fetch(id), so replies land in thegroupsbranch offetchAllowedChannelrather than theallowFrombranch — worked around with/discord:access group add <id> --no-mention. Will file that on the plugin repo separately if it's not already tracked.)