claude-code - ✅(Solved) Fix Telegram plugin: inbound messages not delivered to conversation (outbound works) [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#46744Fetched 2026-04-12 13:34:12
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×5commented ×1

Fix Action

Fix / Workaround

  • Messages sent from Telegram to the bot never appear as <channel source="telegram"> tags in the conversation
  • The bot shows "typing" indicator (line 940 in server.ts fires), confirming the message reaches handleInbound() and passes the gate
  • The MCP notification is dispatched (line 957: mcp.notification({ method: 'notifications/claude/channel', ... })) but Claude Code does not surface it

PR fix notes

PR #1377: fix(telegram): retry polling on all errors, not just 409 Conflict

Description (problem / solution / changelog)

Summary

The poll-loop startup in server.ts only retries 409 Conflict errors. Any other transient error (connection timeout, TLS reset, DNS hiccup) causes bot.start() to reject, the catch block to return, and polling to stop permanently. The plugin process stays alive because MCP stdin keeps it running — so outbound tools (reply, react, edit_message) continue to work while the bot is completely deaf to inbound Telegram messages.

This is one of the root causes behind the widespread "outbound works, inbound doesn't" reports across 65+ open issues.

Root cause analysis

The vulnerable code path (lines 988–1035):

} catch (err) {
  // ...
  if (err instanceof GrammyError && err.error_code === 409) {
    // retry with backoff
    continue
  }
  // ...
  process.stderr.write(`telegram channel: polling failed: ${err}\n`)
  return  // ← ANY non-409 error exits the loop permanently
}

After return, the async IIFE exits. The process stays alive because the MCP server's stdin reader keeps the event loop running. Outbound tool calls work (they create fresh HTTP requests), but grammy's poll loop is dead — no getUpdates calls, no inbound messages.

Evidence (strace on a stalled plugin)

$ timeout 10 strace -p <plugin_pid> -e trace=network,write,read -f
strace: Process <pid> attached
strace: Process <pid> detached
(zero output — zero syscalls in 10 seconds)

FD inspection during the stall:

$ for fd in /proc/<pid>/fd/*; do echo "$(basename $fd) -> $(readlink $fd)"; done
0 -> socket:[...]    # MCP stdin (alive)
1 -> socket:[...]    # MCP stdout (alive)
2 -> socket:[...]    # stderr
3 -> /dev/urandom
4 -> anon_inode:[eventpoll]
# ... all internal FDs, ZERO TCP sockets to api.telegram.org

After applying this fix, the plugin maintained 2 ESTABLISHED TCP connections to 149.154.166.110:443 for 3+ hours and recovered from transient errors automatically.

What this PR changes

  • Catch ALL errors in the poll-loop, not just 409 — retry with exponential backoff (up to 15s)
  • Track consecutive 409s separately so the "another poller is active" give-up logic (8 attempts) is preserved
  • Reset counters on successful bot.start() via the onStart callback
  • Log all retry errors with the error message so operators can see what's happening

What this PR does NOT fix

There is a second, separate bug in Claude Code itself: after ~2–3 hours, Claude Code's internal handler for notifications/claude/channel events stops surfacing inbound messages, even when the plugin is alive and actively polling (confirmed via strace showing active write syscalls and ESTABLISHED TCP sockets). This is tracked in anthropics/claude-code#46744 and others. This PR fixes the plugin-side crash; the Claude Code notification handler degradation is a separate issue.

Related issues

  • anthropics/claude-code#46744 — Telegram plugin: inbound messages not delivered to conversation
  • anthropics/claude-code#46016 — Telegram plugin: inbound messages not delivered to session
  • anthropics/claude-code#46356 — Telegram channel plugin: notifications not injected into conversation
  • anthropics/claude-plugins-official#870 — Inbound Telegram DMs silently dropped
  • anthropics/claude-plugins-official#1345 — inbound messages consumed by server but never rendered as channel tag

Test plan

  • Verified in Docker container (Debian bookworm, Bun 1.x) for 3+ hours
  • Confirmed via strace that plugin maintains TCP connections after fix
  • 409 Conflict handling still works (consecutive409 counter, 8-attempt limit)
  • Graceful shutdown (bot.stop() / "Aborted delay") still exits cleanly
  • Needs testing on macOS and Windows native

🤖 Generated with Claude Code

Changed files

  • external_plugins/telegram/server.ts (modified, +29/-19)

Code Example

{
  "dmPolicy": "pairing",
  "allowFrom": ["<my_telegram_id>"],
  "groups": {},
  "pending": {}
}
RAW_BUFFERClick to expand / collapse

Bug Description

The Telegram plugin (v0.0.5) receives inbound messages from Telegram (shows "typing" indicator) but does not inject them into the Claude Code conversation. Outbound messages via the reply tool work perfectly.

Environment

  • Claude Code: 2.1.101
  • Plugin: telegram@claude-plugins-official 0.0.5 (also tested with 0.0.4)
  • OS: WSL2 Ubuntu 24.04 / Linux 6.6.87.2-microsoft-standard-WSL2
  • Runtime: Bun (bun server.ts)

Configuration

~/.claude/channels/telegram/access.json:

{
  "dmPolicy": "pairing",
  "allowFrom": ["<my_telegram_id>"],
  "groups": {},
  "pending": {}
}
  • Bot token is valid (getMe returns OK)
  • Sender ID is in allowFrom
  • Plugin is enabled in settings.json

What works

  • mcp__plugin_telegram_telegram__reply sends messages to Telegram successfully
  • react and edit_message tools also work
  • Bot process is running and polling (getUpdates returns 409 Conflict confirming exclusive poll)
  • gate() function returns deliver for the allowed sender (verified by reading source)

What doesn't work

  • Messages sent from Telegram to the bot never appear as <channel source="telegram"> tags in the conversation
  • The bot shows "typing" indicator (line 940 in server.ts fires), confirming the message reaches handleInbound() and passes the gate
  • The MCP notification is dispatched (line 957: mcp.notification({ method: 'notifications/claude/channel', ... })) but Claude Code does not surface it

Steps to Reproduce

  1. Configure telegram plugin with valid bot token and sender ID in allowFrom
  2. Start a Claude Code session
  3. Verify plugin is running (ps aux | grep telegram)
  4. Send a message from Telegram to the bot
  5. Bot shows "typing" but no <channel> tag appears in the conversation
  6. Use the reply tool from Claude Code → message arrives in Telegram (outbound works)

Investigation Done

  • Confirmed single plugin process (no zombie/duplicate pollers)
  • Confirmed gate() returns deliver (sender is in allowFrom)
  • Confirmed bot.api.sendChatAction('typing') fires (user sees typing indicator)
  • Confirmed mcp.notification() is called at line 957 of server.ts
  • Killed and restarted plugin — same behavior
  • Tested with both plugin versions 0.0.4 and 0.0.5
  • Plugin connects via StdioServerTransport (line 630)

Expected Behavior

Messages from an allowed Telegram sender should appear in the Claude Code conversation as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">message text</channel> tags.

extent analysis

TL;DR

The issue likely lies in the handling of inbound messages within the Telegram plugin, specifically after the mcp.notification() call, and a fix may involve modifying the plugin's code to properly inject messages into the Claude Code conversation.

Guidance

  • Verify that the mcp.notification() call is correctly formatted and contains all necessary information for the Claude Code conversation to process the inbound message.
  • Check the Claude Code documentation to ensure that the expected behavior of injecting messages as <channel source="telegram"> tags is correctly implemented and configured.
  • Investigate the code path after the mcp.notification() call to identify any potential issues or misconfigurations that might prevent the message from being surfaced in the conversation.
  • Consider adding logging or debugging statements to the Telegram plugin code to gain more insight into the processing of inbound messages and the interaction with the Claude Code conversation.

Example

No code example is provided due to the lack of specific details about the implementation of the mcp.notification() call and the subsequent handling of the notification in the Claude Code conversation.

Notes

The issue may be related to a specific configuration or implementation detail that is not immediately apparent from the provided information. Further investigation and debugging may be necessary to identify the root cause of the issue.

Recommendation

Apply a workaround by modifying the Telegram plugin code to include additional logging or debugging statements to help identify the issue, as the root cause is not immediately clear from the provided information.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING