Discord channel plugin: inbound notifications silently dropped on CC 2.1.278 (typing fires, no session turn) — resurfacing #86566

Status Fixed / completed
Reported on v2.1.278
Maintainer reply None cached
Activity 8 comments · opened Sep 21, 2026 · closed Sep 24, 2026

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 update confirms no newer version)
  • OS: Windows 11 Pro 26200 (PowerShell 5.1)
  • Runtime: Bun (latest)
  • Plan: Personal Pro

Repro

  1. Install discord@claude-plugins-official, configure token via /discord:configure.
  2. Pair a user via /discord:access, confirm dmPolicy: allowlist with the paired user in allowFrom.
  3. Start a fresh claude session in any directory.
  4. Confirm /mcp shows discord connected.
  5. 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.ts process running for this plugin (confirmed via Get-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.exe PID hosting the interactive session.
  • Not a mid-session reload artifact: reproduces after full session restart (fresh claude) and after claude --continue, not just after /reload-plugins.
  • Not a gate drop: typing indicator only fires after gate() returns deliver (see plugin server.ts L852–854), so the message passed the allowlist.
  • Not a .env encoding issue: verified .env starts with ASCII DIS…, ends with \n, no BOM.
  • Outbound MCP fully works: from the CLI I can successfully call mcp__plugin_discord_discord__fetch_messages and mcp__plugin_discord_discord__reply against 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.

View original on GitHub ↗

7 Comments

marcindulak · 24 days ago

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.

hgelectricidad35-cloud · 22 days ago

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.

immefrank · 21 days ago

Ok, let me try and get back to you.

immefrank · 21 days ago

@hgelectricidad35-cloud thanks for the pointer to #59240.

I ran the sandboxed test: installed @anthropic-ai/claude-code@2.1.114 into an isolated directory, launched it with DISABLE_AUTOUPDATER=1 (verified with claude doctor), same ~/.claude/ config and same discord@claude-plugins-official@0.0.4 plugin.

Result: same failure pattern on 2.1.114.

  • /mcp shows plugin:discord:discord ✓ connected (single plugin server, verified only one bun.exe server.ts in the process tree and its parent chain leads to the 2.1.114 claude.exe).
  • DM to bot → typing indicator fires (plugin received the message, gate passed, sendTyping and MCP notification both dispatched).
  • No <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:

  1. The two plugins take different notification paths, and Discord's has been broken since before 2.1.114.
  2. Something plugin-specific in 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.
  3. Independent bug that just happens to share the same visible symptom.

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.

hgelectricidad35-cloud · 21 days ago

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ó:

immefrank left a comment (anthropics/claude-code#95775) <https://github.com/anthropics/claude-code/issues/95775#issuecomment-5806939050> @hgelectricidad35-cloud <https://github.com/hgelectricidad35-cloud> thanks for the pointer to #59240 <https://github.com/anthropics/claude-code/issues/59240>. I ran the sandboxed test: installed @@. into an isolated directory, launched it with DISABLE_AUTOUPDATER=1 (verified with claude doctor), same ~/.claude/ config and same @.@0.0.4 plugin. Result: same failure pattern on 2.1.114. - /mcp shows plugin:discord:discord ✓ connected (single plugin server, verified only one bun.exe server.ts in the process tree and its parent chain leads to the 2.1.114 claude.exe). - DM to bot → typing indicator fires (plugin received the message, gate passed, sendTyping and MCP notification both dispatched). - No <channel source="discord"> turn appears in the CLI. Session JSONL is not appended. So the 2.1.114-vs-later regression boundary from #59240 <https://github.com/anthropics/claude-code/issues/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: 1. The two plugins take different notification paths, and Discord's has been broken since before 2.1.114. 2. Something plugin-specific in @.@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. 3. Independent bug that just happens to share the same visible symptom. 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. — Reply to this email directly, view it on GitHub <https://github.com/anthropics/claude-code/issues/95775?email_source=notifications&email_token=B223LGCNMRSBBDCOT45WS3T5QSHYVA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOBQGY4TGOJQGUYKM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5806939050>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/B223LGBZP5DXSQ6OCUQ5BE35QSHYVAVCNFSNUABFKJSXA33TNF2G64TZHM4TGNZSGUZTINZVHNEXG43VMU5TKNJSGI2DMMRRGQZKC5QC> . Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS <https://github.com/notifications/mobile/ios/B223LGFP5SPH7VLEFH7ZIS35QSHYVA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOBQGY4TGOJQGUYKM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y> and Android <https://github.com/notifications/mobile/android/B223LGHCECFPS3MAWXDO4GT5QSHYVA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOBQGY4TGOJQGUYKM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>. Download it today! You are receiving this because you were mentioned.Message ID: @.>
immefrank · 21 days ago

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.json command pointed at a Node script that forks the plugin's bun ... start and logs stdin/stdout of both sides.

1. Initialize (CC → plugin)

{
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": { "roots": {}, "elicitation": {} },
    "clientInfo": {
      "name": "claude-code",
      "version": "2.1.114"
    }
  },
  "jsonrpc": "2.0",
  "id": 0
}

Client capabilities are roots and elicitation only. No experimental.claude/channel (or experimental.claude/channel/permission) declared by the client.

2. Initialize response (plugin → CC)

{
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "tools": {},
      "experimental": {
        "claude/channel": {},
        "claude/channel/permission": {}
      }
    },
    "serverInfo": { "name": "discord", "version": "1.0.0" }
  },
  "jsonrpc": "2.0",
  "id": 0
}

Server advertises the experimental capabilities.

3. Gateway connect (plugin stderr)

discord channel: gateway connected as Kanijo-AI#0052

4. Inbound DM notification (plugin → CC) — ~20s later

{
  "method": "notifications/claude/channel",
  "params": {
    "content": "wire tee test",
    "meta": {
      "chat_id": "…",
      "message_id": "…",
      "user": "immefrank",
      "user_id": "…",
      "ts": "2026-09-24T03:48:05.586Z"
    }
  },
  "jsonrpc": "2.0"
}

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/channel but 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, strings on the CC 2.1.114 binary does show notifications/claude/channel, notifications/claude/channel/permission, and notifications/claude/channel/permission_request embedded (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 client initialize — meaning the server's negotiation path never enables the dispatch.

If that reading is right, the fix is one of:

  1. CC declares experimental.claude/channel (and .../permission) in its initialize clientCapabilities whenever the discord/telegram/imessage channel plugins are loaded (or unconditionally, if the handler is universally present).
  2. Or the plugin should emit the notification regardless of declared client capabilities (arguably against MCP spec, but explains why it "should" have worked).

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 content and meta fields 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 of notifications/claude/channel rather than anything specific to the Discord plugin.

If a full un-redacted trace or the tee wrapper itself would be useful, happy to share.

immefrank · 21 days ago

Closing this — turned out to be operator error on my end, not a Claude Code bug.

Root cause: launching claude without the --channels plugin:discord@claude-plugins-official flag. Per the Channels docs, the flag is required every session; having the plugin in .mcp.json alone 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's initialize frame advertises only {roots, elicitation} — no experimental.claude/channel — so the server-emitted notifications/claude/channel is 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):

claude --channels plugin:discord@claude-plugins-official

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 like discord 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 reply seems to see the same DM channel as a non-DM type when re-fetched via client.channels.fetch(id), so replies land in the groups branch of fetchAllowedChannel rather than the allowFrom branch — worked around with /discord:access group add <id> --no-mention. Will file that on the plugin repo separately if it's not already tracked.)

Showing cached comments. Read the full discussion on GitHub ↗