MCP channel notifications silently dropped when session is idle

Resolved 💬 4 comments Opened Mar 21, 2026 by FrankAtGHub Closed Apr 18, 2026

Summary

MCP channel notifications (notifications/claude/channel) are silently dropped when a Claude Code session is waiting for user input. This makes bidirectional channel plugins (Telegram, Slack, etc.) effectively one-way — the agent can send messages out, but cannot receive inbound messages unless the session happens to be in an active conversation turn.

Steps to Reproduce

  1. Configure the official Telegram plugin (telegram@claude-plugins-official)
  2. Pair with a Telegram user (allowlist mode)
  3. Confirm outbound works: use the reply tool to send a message — succeeds
  4. Let the session go idle (waiting for user input at the prompt)
  5. Send a Telegram message to the bot from the paired user
  6. The bot receives the message, calls mcp.notification() with method: 'notifications/claude/channel'
  7. The notification is never delivered to the session. The user's message is silently lost.

Messages only arrive as <channel> tags when the session is actively processing a conversation turn. If the agent is idle, the notification has nowhere to go.

Impact

This makes channel plugins unreliable for real-world use. The primary use case — "message my agent when I'm away and have it respond" — doesn't work. Users send messages expecting a response and get silence.

Workaround

We implemented a two-part workaround that restores full bidirectional messaging:

1. Spool file patch (plugin side):
In the Telegram plugin's handleInbound function, after the existing mcp.notification() call, also write the message to a local spool file:

// Write inbound messages to spool for external delivery
try {
  const spoolDir = join(STATE_DIR, 'spool')
  mkdirSync(spoolDir, { recursive: true })
  const entry = JSON.stringify({
    ts: new Date().toISOString(),
    chat_id, message_id: msgId ? String(msgId) : undefined,
    user: from.username ?? String(from.id),
    text,
  }) + '\n'
  appendFileSync(join(spoolDir, 'incoming.jsonl'), entry)
} catch {}

2. Nudge daemon (session side):
A lightweight bash script polls the spool file every 2 seconds. When a new message appears, it uses tmux send-keys to inject the message text directly into the Claude Code session's terminal input:

tmux send-keys -t "$TARGET_SESSION" "[Telegram from $USER]: $TEXT" Enter

This works because tmux send-keys writes to the terminal's stdin, which Claude Code reads as user input — even when idle. The message appears as a user prompt and the agent processes it normally.

The daemon also supports @mention routing (e.g., @architect routes to a different tmux session), enabling multi-agent Telegram dispatch from a single bot.

Proposed Fix

The proper fix should be in Claude Code's MCP notification handler. When a notifications/claude/channel notification arrives and the session is idle, it should be queued and delivered as a synthetic user prompt (similar to how our tmux send-keys workaround works). This would make all channel plugins work bidirectionally without external daemons.

Environment

  • Claude Code (latest as of 2026-03-21)
  • Plugin: telegram@claude-plugins-official
  • Platform: Linux (Ubuntu 24.04)
  • Session type: tmux

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗