How Claude Code Channels Work
Claude Code v2.1.80 introduced Channels in research preview — a capability that lets you push events into a running Claude Code session from the outside world.
This post is kept up to date. Jump to the flags and settings reference or the latest changes if that is what you came for. A CI build failure, a Telegram message, a monitoring alert: any of these can now wake Claude up and trigger action, without you having to type a thing.
AgentRQ has been building on this pattern since before it had an official name. Here's how channels work under the hood, and how to build your own.
What Is a Channel?
A channel is an MCP server that pushes events into Claude Code rather than only responding to tool calls from it. Claude Code spawns your channel as a subprocess and communicates over stdio — standard MCP. The channel-specific part is declaring the claude/channel experimental capability in the Server constructor, which registers a notification listener inside Claude Code.
Once registered, your server can call mcp.notification() at any time with method notifications/claude/channel. Claude Code wraps the payload in a <channel> tag and injects it into Claude's context:
Claude sees this tag, understands what happened from the source and attributes, and takes action — all without you typing anything.
One-Way vs. Two-Way
Channels come in two flavors:
- → One-way: your server pushes events, Claude acts in the session. Good for CI alerts, monitoring webhooks, or any "fire and forget" notification.
- → Two-way: your server also exposes a
replyMCP tool that Claude can call to send messages back. Good for chat bridges (Telegram, Discord, iMessage).
A two-way channel can also opt into permission relay: when Claude needs to approve a tool call, the prompt can be forwarded to your channel so you can approve or deny it remotely — from your phone, for example.
The Minimum Viable Channel
Three things are required:
- Declare
capabilities.experimental['claude/channel']: {}in your Server constructor - Emit
notifications/claude/channelnotifications when events arrive - Connect over stdio (Claude Code spawns your server as a subprocess)
Here's a complete one-way webhook receiver in about 30 lines:
Register it in .mcp.json:
Start Claude Code with the development flag (required during the research preview, since custom channels aren't on the approved allowlist yet):
Send it a test payload:
Claude receives it and acts. No manual intervention needed.
Notification Format
Your server pushes events by calling mcp.notification() with two params:
- →
content— the event body, delivered as the text inside the<channel>tag. - →
meta— optional key-value pairs, each becoming an attribute on the tag. Keys must use letters, digits, and underscores only.
The source attribute is set automatically from your server's configured name. So this notification:
Arrives in Claude's context as:
The instructions string in your Server constructor goes into Claude's system prompt. Use it to tell Claude what events to expect, what the attributes mean, whether to reply, and if so which tool to use.
Adding Replies (Two-Way Channels)
To make a channel two-way, add tools: {} to capabilities and register two MCP request handlers:
- →
ListToolsRequestSchema— describes yourreplytool so Claude knows it exists - →
CallToolRequestSchema— implements the send logic when Claude calls it
Then update instructions to tell Claude when and how to use the reply tool, and which <channel> attribute to pass back as the chat_id. Here's the key addition:
Sender Gating
An ungated channel is a prompt injection vector — anyone who can POST to your endpoint can put arbitrary text in front of Claude. Always gate on sender identity before emitting:
One important detail: gate on sender identity (message.from.id), not room identity (message.chat.id). In group chats these differ — gating on the room would let anyone in an allowlisted group inject prompts into your session.
The official Telegram and Discord channels use a pairing flow: the user DMs the bot, the bot replies with a code, the user approves it in their Claude Code session, and their platform ID gets added to the allowlist.
Permission Relay
Two-way channels can forward tool-approval prompts to you remotely. When Claude wants to run Bash or Write, the approval dialog can appear in your chat app — not just the local terminal. Both stay live: the first answer wins.
To opt in, add claude/channel/permission: {} under experimental capabilities. Then:
- Handle
notifications/claude/channel/permission_request— format the prompt and send it through your platform API, including therequest_id - In your inbound handler, check for replies matching
yes <id>orno <id>and emit anotifications/claude/channel/permissionverdict
The regex for matching verdicts is /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i. The five-letter ID alphabet skips l so it's never confused with 1 when typed on a phone.
Here's the verdict emitter:
How AgentRQ Fits In
AgentRQ implements a production-grade version of this exact pattern. When Claude Code connects to AgentRQ via MCP, it gets:
- → Inbound tasks from humans delivered via channel notifications
- → Reply and createTask tools for bidirectional communication
- → Human-in-the-loop approvals for reviewing Claude's planned actions
- → Attachment support for sharing files between Claude and humans
The key difference: AgentRQ routes messages through a hosted service so you don't have to run a local server or manage an allowlist. You receive and respond to tasks from the AgentRQ app on any device.
This means you can have full human-in-the-loop oversight of Claude Code sessions from anywhere — while Claude keeps working on your machine.
Requirements and Limitations
Channels require Claude Code v2.1.80 or later and Anthropic authentication: either a claude.ai login or, since v2.1.128, a Console API key. They are not available through Amazon Bedrock, Google Cloud or Microsoft Foundry. The official channel plugins need Bun installed. Team and Enterprise organizations must enable channels before anyone can use them (see the reference below).
During the research preview, your custom channel won't be on the approved allowlist, so you need the development flag:
To have your channel added to the allowlist, submit it to the official marketplace. Channels go through security review before approval. Organizations can also maintain their own allowlist with the allowedChannelPlugins managed setting.
Claude Code Channels Reference
Every flag and setting people ask about, checked against the Claude Code documentation and changelog on October 2, 2026.
Status
Channels are still a research preview as of Claude Code 2.1.287 (October 1, 2026). Anthropic says the flag syntax and protocol may change. Neither --channels nor the development flag appears in claude --help during the preview.
The channels flag
--channels takes a space-separated list of plugin:<name>@<marketplace> entries and turns on channel notifications from those servers for this one session. Having a server in .mcp.json is not enough: it only pushes messages into the session if it is also named here. Each entry must be on Anthropic's allowlist, or on your organization's allowedChannelPlugins list. If it is not, Claude Code still starts, the channel just doesn't register, and a notice at startup says why. Since v2.1.281 the plugin name has to match the allowlist entry, not only the marketplace.
The dangerously-load-development-channels flag
This flag exists because custom channels are not on the allowlist during the preview. It accepts server:<name> for a plain server in your .mcp.json, or a plugin: entry. It skips the allowlist check only, for each entry you list, after a confirmation prompt. It does not extend to entries passed with --channels, and it does not get around an organization that has channels turned off. This is the flag AgentRQ uses: claude --dangerously-load-development-channels server:agentrq-<workspace>.
Who can use channels
| Account | Channels |
|---|---|
| Pro or Max, no organization | Available, no admin step |
| claude.ai Team or Enterprise | Off until an Owner enables it in Admin settings → Claude Code → Channels, or sets the managed channelsEnabled: true |
| Console (API key) | Available since v2.1.128, unless the organization deploys managed settings, which then need channelsEnabled: true |
| Bedrock, Google Cloud, Foundry | Not available |
allowedChannelPlugins is a managed setting that replaces Anthropic's default allowlist for an organization. An empty list blocks every channel plugin except those loaded with the development flag.
Official channels: Telegram, Discord and iMessage
Anthropic publishes three channels in the claude-plugins-official marketplace, plus a fakechat demo that runs on localhost:
- → Telegram:
/plugin install telegram@claude-plugins-official, then/telegram:configure <bot token>, restart with--channels plugin:telegram@claude-plugins-official, and pair your account with/telegram:access pair <code>. - → Discord: the same steps with
discordin place oftelegram. - → iMessage: macOS only and needs no token. Messages you send to yourself are let through automatically, and
/imessage:access allow <handle>adds other senders.
If the marketplace is missing, add it first with /plugin marketplace add anthropics/claude-plugins-official.
The MCP side
A channel server declares capabilities.experimental["claude/channel"] and sends notifications/claude/channel with a content string and optional meta. Each meta key becomes an attribute on the <channel> tag Claude sees, and keys may only use letters, digits and underscores. To relay permission prompts, the server also declares experimental["claude/channel/permission"]. Claude Code then sends notifications/claude/channel/permission_request, and the server answers with notifications/claude/channel/permission carrying allow or deny. The terminal prompt stays open too, and whichever answer arrives first wins.
preferredNotifChannel is not a channel
People often find preferredNotifChannel while searching for channels. It is unrelated: it decides how Claude Code tells you on your own machine that a task finished or is waiting for permission. Values are auto (the default), terminal_bell, iterm2, iterm2_with_bell, kitty, ghostty and notifications_disabled. Set it in /config under local notifications, or in ~/.claude/settings.json:
Older guides say to run claude config set --global preferredNotifChannel terminal_bell. The claude config command was removed in Claude Code 2.0, so that no longer works.
Claude Code Channels Latest Changes
Last checked October 2, 2026. Dates are npm release dates.
| Version | Date | Change |
|---|---|---|
| 2.1.281 | 2026-09-23 | --channels plugin entries must match the plugin name, not just the marketplace |
| 2.1.267 | 2026-09-09 | An unreadable managed allowedChannelPlugins now admits nothing; string entries like "plugin@marketplace" accepted |
| 2.1.234 | 2026-08-17 | Permission previews go only to channel servers that passed the sender gate; an explicit opt-out is honored |
| 2.1.211 | 2026-07-15 | Relayed permission previews strip hidden and look-alike characters |
| 2.1.187 | 2026-06-23 | Channel connections no longer drop after the agents view, /bg, /tui or /update |
| 2.1.128 | 2026-05-04 | Channels work with Console API keys; Console orgs with managed settings need channelsEnabled: true |
| 2.1.126 | 2026-04-30 | Plan mode tools work again in interactive --channels sessions |
| 2.1.105 | 2026-04-13 | Team and Enterprise: inbound messages no longer dropped after the first one |
| 2.1.84 | 2026-03-25 | allowedChannelPlugins managed setting added |
| 2.1.83 | 2026-03-24 | Fixed "Channels are not currently available" after upgrading |
| 2.1.81 | 2026-03-20 | Permission relay over channels added |
| 2.1.80 | 2026-03-19 | --channels added as a research preview |
Next Steps
The official Claude Code channel implementations for Telegram, Discord, and iMessage are the best reference for production patterns: pairing flows, file attachment handling, and full permission relay.
If you'd rather skip the infrastructure and get straight to human-in-the-loop collaboration, AgentRQ connects Claude Code to you in 60 seconds — no server to run.