claude-code - 💡(How to fix) Fix Built-in channel-reply guardrail for channel-routed plugins (e.g. Telegram)

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…

Error Message

The model's default output mode is text into the session transcript, which does not reach the channel user. When the agent forgets to wrap a response in the reply tool call, the user receives nothing — a silent failure with no visible error to either side.

Code Example

{"decision":"block","reason":"Channel message unanswered — call the reply tool before ending the turn."}
RAW_BUFFERClick to expand / collapse

Problem

When Claude Code is bound to a channel-routed plugin — for example claude --channels plugin:telegram@claude-plugins-official — the agent receives inbound messages via the channel and is instructed (in the plugin's MCP instructions) to respond via a specific reply tool (e.g. mcp__plugin_telegram_telegram__reply).

The model's default output mode is text into the session transcript, which does not reach the channel user. When the agent forgets to wrap a response in the reply tool call, the user receives nothing — a silent failure with no visible error to either side.

This is not a plugin bug. It's a behavior failure mode: the reply-tool requirement is a soft instruction in the system prompt that the model can and does drop, especially on long structured responses or in long sessions where the instruction drifts out of attention.

Reproduction

  1. Run claude --channels plugin:telegram@claude-plugins-official and pair the bot.
  2. Send 4–6 messages from the channel side, asking questions that elicit long structured replies (tables, multi-section analyses).
  3. Observe Claude producing responses in the CLI transcript but intermittently failing to fire the mcp__plugin_telegram_telegram__reply tool.
  4. The channel user sees long silent gaps, eventually asks "why no response?"

Personally hit this 4+ times in a single session even after explicitly acknowledging the failure mode mid-conversation. Memory/feedback alone does not reliably fix it.

Proposed Solution

Ship a built-in Stop hook (opt-in via channelsEnabled: true, or auto-enabled for any session bound with --channels) that:

  1. Scans the current session transcript (.jsonl) for the most recent inbound channel event (a type:queue-operation line whose content contains a <channel source="..."> tag).

  2. Checks every line after that point for an assistant tool_use whose name matches the channel plugin's registered reply tool.

  3. If a channel arrival exists with no subsequent reply tool call, returns:

    {"decision":"block","reason":"Channel message unanswered — call the reply tool before ending the turn."}

    to force the model to send the reply before turn-end.

Per the update-config skill's own doctrine: "automated behaviors require hooks configured in settings.json — the harness executes these, not Claude, so memory/preferences cannot fulfill them." This is exactly that.

Implementation Reference

Built this manually for the Telegram plugin in ~60 lines of bash. Generic version would need:

  • Reply-tool-name registration in the channel plugin manifest (e.g. a replyToolName field that the hook reads).
  • Per-plugin or per-session opt-out for cases where Claude legitimately shouldn't reply (e.g. a user explicitly said "don't respond").
  • Probably a one-line addition to plugin-install flow so users get the guardrail automatically when they pair a channel plugin.

Why It Matters

Without this, channel-routed sessions — which are the Claude Code feature most likely to be used as an "always-on assistant" pattern — are unreliable in a way that's invisible until it breaks trust. The user only finds out by noticing silence and asking. By that point you've burned a chunk of credibility.

A built-in guardrail makes channel integration production-grade by default, instead of every user needing to discover the failure mode and write their own hook.

Related

  • update-config skill description (the doctrine that this implements)
  • Existing channel plugin: plugin:telegram:telegram and the reply/react/edit_message/download_attachment tools
  • Hook events: Stop is the natural choice; SubagentStop may also apply for delegated work

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