Feature request: surface AskUserQuestion through the MCP channel API so plugins can proxy it
Summary
Today, the AskUserQuestion tool renders its option-picker exclusively in the
local Claude Code TUI. When a user runs Claude Code with an MCP channel plugin
(Telegram, iMessage, fakechat) and is physically away from the machine, an
AskUserQuestion call stalls the session indefinitely: the picker is invisible
on the channel surface, and there's no way to answer it without returning to
the TUI.
The existing permission-relay extension (claude/channel/permission_request)
already proves the proxying pattern works. A parallelclaude/channel/ask_question_request capability would let plugin servers
render the picker on whichever surface the user is reading and route the
answer back.
Why this matters
Concrete failure case (2026-05-14): a personal-agent Claude Code session
running on a remote Mac mini stalled mid-conversation on an AskUserQuestion
fired by a skill. The user was remote on Telegram, saw nothing change, and
the session sat blocked for an extended period until someone returned to the
machine. Inbound Telegram messages queued unread the whole time.
The class of failure isn't specific to one project. Any setup where:
- a plugin is configured (Telegram, iMessage, fakechat), AND
- the user is reading messages on the plugin surface,
is exposed to it whenever a skill or the model itself fires AskUserQuestion.
Today's only mitigation is operator discipline ("don't call AskUserQuestion
when the user is remote"), enforced by user memory or PreToolUse hooks. A
first-class channel proxy is the durable fix.
Proposed shape
Mirror the permission-prompt path:
Outbound (Claude Code → plugin):
A new notification kind notifications/claude/channel/ask_question_request
emitted to MCP servers that declare theexperimental.claude/channel/ask_question capability. Suggested payload:
{
method: 'notifications/claude/channel/ask_question_request',
params: {
request_id: string,
questions: Array<{
question: string,
header: string, // short chip label
multiSelect: boolean,
options: Array<{
label: string,
description?: string,
preview?: string,
}>,
}>,
},
}
Inbound (plugin → Claude Code):
The plugin renders the question(s) on its surface (e.g. Telegram inline
keyboard), collects the answer, and replies via a notification:
{
method: 'notifications/claude/channel/ask_question_answer',
params: {
request_id: string,
answers: Record<string, string>, // matches the existing AskUserQuestion result schema
},
}
Claude Code resolves the original AskUserQuestion tool call with that payload.
Capability declaration
Servers opt in by declaring experimental.claude/channel/ask_question in
their capabilities — same pattern as the permission extension. The Telegram
plugin and the fakechat plugin both already implement a similar capability
declaration; this would be a one-extra-key addition for each.
Acceptance criteria
- A Claude Code session with the Telegram plugin and a remote-only user can
complete a workflow that hits AskUserQuestion entirely via Telegram —
message-arrival, button-tap, answer round-trip.
- Multi-question batches (1–4 questions per AskUserQuestion call) render as
distinct UI elements on the plugin surface; the tool call resolves only
when every question is answered.
- The plugin can replace a question's keyboard with a "✅ Selected: …" line
after the user answers, so the same prompt can't be answered twice and the
chat history records the choice.
- The "Other" / free-text answer path is supported — plugins can render a
"Type something" button that switches the question into free-text mode and
captures the user's next text message as the answer.
What I've already verified
Across the three official channel plugins (telegram/0.0.6, imessage/0.1.0,fakechat/0.0.1), only two claude/channel/* namespaces are declared today:claude/channel (inbound messages) and claude/channel/permission. There is
no ask_question namespace, so plugin-only fixes are impossible without this
upstream change.
The local PreToolUse-hook safety net (deny AskUserQuestion outright with a
"pick a sensible default" message to the model) handles the worst case but is
a workaround, not a proxy. It loses the user's actual choice.
Related
- Permission-relay extension (
claude/channel/permission) — proven proxy
shape this request mirrors.
- TomBot project tracking:
imp-20260514-01.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗