Claude Code 的 channels 怎么用、怎么排查:reference 页的字段表怎么读
channels 这个功能的定位很窄:它是一个 MCP server,把外部发生的事件推进你已经开着的那个 Claude Code 会话里。官方文档《Push events into a running session with channels》写明,事件只在会话打开时才会到达,所以想做常驻监听就得把 Claude 跑在后台进程或者常驻终端里。
先把状态说清楚:文档页首的 Note 明确写了 channels 处于 research preview,需要通过 claude.ai 或 Console API key 做 Anthropic 认证,且在 Amazon Bedrock、Google Cloud 的 Agent Platform、Microsoft Foundry 上不可用;Team 与 Enterprise 组织必须由管理员显式启用。文档自己也说了 --channels 的标志语法与协议约定可能随反馈变化。
这篇不重复安装流程,只做一件事:把 code.claude.com/docs/en/channels-reference 那几张字段表讲清楚,然后把由字段理解偏差引起的几类配置错误按排查顺序走一遍。
先把三张表的字段语义摆正
第一张:Server 构造器选项。 reference 页的 Server options 表列了四个字段,其中 instructions 和 capabilities.tools 是标准 MCP 的东西,另外两个是 Claude Code 的扩展:
| 字段 | 类型 | 语义 |
|---|---|---|
capabilities.experimental['claude/channel'] | object | 必填,永远是 {}。它存在这件事本身就注册了通知监听器 |
capabilities.experimental['claude/channel/permission'] | object | 可选,永远是 {}。声明本 channel 可以接收权限中继请求 |
capabilities.tools | object | 仅双向 channel 需要,永远是 {},用于让 Claude 发现回复工具 |
instructions | string | 文档标为 Recommended。这段字符串会进入 Claude 的 system prompt |
这张表里最容易被读漏的是「Always {}」。这几个键的值不承载任何配置,起作用的是键在不在。想做单向 channel,文档写的是 omit capabilities.tools——是整个字段不写。instructions 那一栏文档给的写法建议也很具体:告诉 Claude 会收到什么事件、<channel> 标签的各个属性是什么意思、要不要回复、用哪个工具回、把哪个属性回传(文档举的例子是 chat_id)。
第二张:通知负载。 你的 server 发的是 notifications/claude/channel,params 只有两个字段:
| 字段 | 类型 | 语义 |
|---|---|---|
content | string | 事件正文,会成为 <channel> 标签的 body |
meta | Record<string, string> | 可选。每一项变成 <channel> 标签上的一个属性 |
meta 这一栏有一句非常要命的限制,原文是键必须是标识符:只允许字母、数字和下划线,含连字符或其它字符的键会被静默丢弃(silently dropped)。这条后面还会再提。
source 属性不用你填,文档写明它由 server 配置的 name 自动生成。事件到达 Claude 上下文时长这样:
<channel source="your-channel" severity="high" run_id="1234">
build failed on main: https://ci.example.com/run/1234
</channel>
第三张:权限中继的请求字段。 通知方法是 notifications/claude/channel/permission_request,文档说 params 有四个字符串字段:request_id、tool_name、description、input_preview。request_id 的定义写得很细——五个小写字母,取自 a-z 但跳过 l,文档自述的理由是免得在手机上输入时被看成 1 或 I;而且本地终端对话框不显示这个 ID,你的出站处理函数是唯一能拿到它的地方。
description 那一栏也得记住:它是这次工具调用的人话摘要,从不是命令本身;模型没给描述时,该字段就是常量 Run shell command,不带任何命令细节,真正的参数在 input_preview 里。回传的裁决是 notifications/claude/channel/permission,两个字段:request_id 回显,behavior 取 'allow' 或 'deny';文档明确写了裁决不影响后续调用。
排查一:事件发出去了,会话里什么都没有
现象:你的 server 调了 mcp.notification(),await 也顺利返回,但会话里没有任何 <channel> 行。
怎么确认。 先接受一个前提:文档写明 Claude Code 不对通知做确认,mcp.notification() 的 await 只在消息写进传输层时 resolve,不代表 Claude 处理了;会话没把你的 server 当 channel 加载、或者组织策略拦了,事件会被静默丢弃且不给你的 server 任何错误。判定动作因此全在会话这一侧:
- 在会话里运行
/mcp看 server 状态。文档说 “Failed to connect” 通常是 server 文件里的依赖或 import 出错,去看~/.claude/debug/<session-id>.txt里的 stderr 追踪。 - 看启动横幅下面那行提示,文档给了原文:
Channels (experimental) messages from server:webhook inject directly in this session · restart without --dangerously-load-development-channels to stop。没有这一行就说明 channel 根本没注册;提示里写了 “blocked by org policy” 则是组织没启用。
处置。 按判定结果分三种:
- server 连上了但没注册成 channel:文档有一句直白的话——在
.mcp.json里不算数,server 还必须被--channels点名。 - 自建 channel:research preview 期间
--channels只接受 Anthropic 维护的允许列表(或管理员设的组织列表)里的插件,自建的一律不在上面,测试要用--dangerously-load-development-channels:
# Testing a plugin you're developing
claude --dangerously-load-development-channels plugin:yourplugin@yourmarketplace
# Testing a bare .mcp.json server (no plugin wrapper yet)
claude --dangerously-load-development-channels server:webhook
这里有个反直觉的点,文档写得很清楚:这个绕过是按条目生效的,把它和 --channels 一起用,不会把绕过延伸到 --channels 里的条目。另外该标志只跳过允许列表,channelsEnabled 这条组织策略照样管着。
- 组织侧被拦:管理员通过两个用户无法覆盖的 managed settings 控制——
channelsEnabled是总开关,allowedChannelPlugins决定哪些插件能注册。这里有个很容易踩的坑:文档写明channelsEnabled没启用时,MCP server 仍然会连上、它的工具也照常能用,只是 channel 消息不会到达,所以/mcp里一切正常完全不能说明 channel 通了。
处置后怎么验证。 文档给的最小闭环是 webhook 例子:server 监听本机端口,外部 POST 进来就转发:
curl -X POST localhost:8788 -d "build failed on main: https://ci.example.com/run/1234"
文档写明终端把事件渲染成一行摘要 ← webhook: build failed on main: ...,而模型收到的是完整的 <channel> 标签。两边形态不一样,别拿终端那行去推断模型看到了什么。
什么情况说明不是这个原因。 如果 curl 直接报 connection refused,那跟 channel 注册无关——文档说这时候是端口没绑上,或者上一轮的残留进程还占着。文档给的判定命令是 lsof -i :<port>(Unix 系工具),看清谁在监听,然后 kill 掉残留进程再重启会话。官方文档没有给出 Windows 侧的对应命令;Windows 上查端口占用(netstat -ano 配合 findstr 过滤端口,再按 PID 结束进程)属于通用运维做法,不是该产品官方文档的内容。
排查二:事件到了,但 <channel> 标签上少了属性
现象:content 正常显示,你精心传的路由信息(会话 ID、告警级别之类)在标签上找不到,Claude 因此不知道该回哪儿。
怎么确认。 逐个检查 meta 的键名:只留字母、数字、下划线。文档原文说键必须是标识符,含连字符或其它字符的键会被静默丢弃——不报错,不降级,键就是没了。文档自己的示例键全部符合这个约束:severity、run_id、chat_id、path、method;写成 run-id、chat.id 就属于会被丢掉的那一类。
处置。 把键改成标识符形式,例如 run-id → run_id,同时把 instructions 里对属性名的描述一起改掉,否则 Claude 按旧名去取会取空。
验证。 参照文档的示例调用发一条带 meta 的事件,确认标签上属性齐了:
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: 'build failed on main: https://ci.example.com/run/1234',
meta: { severity: 'high', run_id: '1234' },
},
})
什么情况说明不是这个原因。 属性都在、Claude 却没按预期回复,问题在 instructions 或 tools 声明上,不在 meta:少了 tools: {},Claude Code 发现不了你的回复工具;instructions 没写清楚回传哪个属性,Claude 也无从知道该把 chat_id 传回去——文档把这两件事和 tool handler 并列为回复工具的三个组成部分。
另一种常被误判成「属性丢了」的情况是多条通知挤在一起。文档写明事件按顺序排队进会话,Claude 忙的时候到达的若干条会在下一轮一起交付、被当成一组处理;想让互相独立的事件流并发处理,文档给的办法是跑多个会话。
排查三:远端回了 yes,本地对话框还开着
现象:权限中继看着接通了,你在手机那头回复了裁决,但本地终端的对话框一直没关。
怎么确认。 文档把失败拆成两种,两种情况下本地对话框都保持打开:格式不对时,入站正则没匹配上,approve it 或不带 ID 的 yes 会被当成普通消息转发给 Claude;格式对、ID 不对时,server 发出了裁决,但 Claude Code 找不到对应的未决请求,静默丢弃。判定动作因此是:看发出去的提示里有没有原样带上 request_id,再看入站正则。文档给的正则和理由写在一起:
// matches "y abcde", "yes abcde", "n abcde", "no abcde"
// [a-km-z] is the ID alphabet Claude Code uses (lowercase, skips 'l')
// /i tolerates phone autocorrect; lowercase the capture before sending
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i
[a-km-z] 正是「跳过 l」的写法,/i 是为了容忍手机自动更正把回复的字母改成大写(文档原话是 tolerates phone autocorrect capitalizing the reply),所以捕获到的 ID 要先转小写再回传。
处置。 三件事缺一不可,文档列得很明确:experimental 下加 claude/channel/permission: {};注册 notifications/claude/channel/permission_request 的通知处理函数,把提示格式化后发出去;在入站处理函数里先于聊天转发那一支识别 yes <id> / no <id>,命中就发裁决通知、不再当聊天转发。顺带一个边界:中继覆盖 Bash、Write、Edit 这类工具调用审批,项目信任对话框和 MCP server 同意对话框不中继,只在本地终端出现。
验证。 文档给的三终端验证法:一个跑会话,一个 curl -N localhost:8788/events 盯出站,一个发消息。它还特意提醒,测试前要用 Shift+Tab 切到 manual mode,因为 auto mode 下分类器会自己决定 reply 调用,根本不会开对话框给远端来答。本文里的命令与代码片段均照抄官方文档,未经实测;research preview 期间标志语法与协议约定都可能变,以官方文档最新内容为准。
什么情况说明不是这个原因。 对话框在你远端回复之前就自己关了,多半是有人在终端先答了——文档写明本地对话框全程保持打开,谁先答用谁的,另一边的未决请求被丢弃。还有一类完全不同的情况:--dangerously-skip-permissions 之下仍会提示的那几项,文档列了五条——显式的 ask 规则、组织把 connector 工具设为 ask 的、标了 requiresUserInteraction 的 MCP 工具、指向 / 或家目录的删除操作、跨会话消息的安全保护。撞上这五条里的任何一条,问题不在中继实现上。
两个不能含糊的安全约束
第一,先做发送方校验,再声明中继能力。文档的原话是:只有当你的 channel 对发送方做了认证时才声明这个能力,因为任何能通过你的 channel 回复的人,都能批准或拒绝你会话里的工具调用。校验要对着发送者身份做,文档举的是 message.from.id 而不是 message.chat.id——群聊里这两个不是一回事,按房间放行等于让群里任何人都能往会话里注入内容。「没有网关的 channel 就是一条 prompt injection 路径」这句也是文档自己写的:任何能够到达你端点的人都可以把文本摆到 Claude 面前。
第二,关于 input_preview 和 description,文档有一段版本相关的说明:v2.1.211 及以后的客户端会在中继前对这两个字段做净化——中和方向覆盖字符与不可见字符、处理引号与尖括号的形似字符、把连续空白折叠成一个空格,超长的值保留首尾并在中间放一个带计数的省略标记;更早的客户端则原样中继 description、并把 input_preview 截断加省略号。文档给的结论很直接:除非你能控制客户端版本,否则把这两个字段都当作不可信输入处理。
Windows 侧几句实在话
预置的那几个 channel 插件(research preview 里给的是 Telegram、Discord、iMessage,外加本地演示用的 fakechat)文档写明都是 Bun 脚本,检查命令是 bun --version。但自己写 channel 并不要求 Bun——文档说硬性要求只有 @modelcontextprotocol/sdk 加一个 Node.js 兼容运行时,Bun、Node、Deno 都行。
iMessage 这个 channel 明确要求 macOS,它直接读 Messages 数据库并通过 AppleScript 发送,Windows 上不用考虑。Telegram 与 Discord 的 token,文档给的落盘位置写作 ~/.claude/channels/telegram/.env 与 ~/.claude/channels/discord/.env,也可以在启动 Claude Code 前把 TELEGRAM_BOT_TOKEN、DISCORD_BOT_TOKEN 设进 shell 环境变量;官方文档没有单独说明 Windows 下这两个路径怎么写,我们不替它推断。
最后一个容易让人以为「功能没了」的点:文档写明 preview 期间 --channels 和 --dangerously-load-development-channels 都不会出现在 claude --help 里,但两个标志都能用。
channels 目前就是这个形态:字段少、约束硬、失败大多是静默的——meta 键被丢、事件被丢、裁决被丢,都不报错。排查时别指望错误信息。
本文依据 Claude Code 官方文档(code.claude.com/docs)于 2026-08-17 的公开内容整理。
该产品闭源,本文只复述官方文档写明的机制,不推断其内部实现;
我们没有对文中涉及的功能做过实测,因此不涉及界面外观、操作手感与运行速度的任何描述。
该产品迭代频繁,文中涉及的命令、配置项与默认值随版本变动,请以官方文档最新内容为准。
本文不涉及价格、额度与限流的具体数值,相关信息请以官方定价与用量说明页为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。
相关阅读
- Claude Code 流式响应中断怎么办?Connection closed、Socket is closed、Stream idle timeout 三条分开看
- 大仓库里 Claude Code 变慢、找不到文件:官方 large codebases 页给出的做法
- 让 Claude Code 走自建 LLM 网关:connect 与 rollout 的两阶段
- MCP 连不上:微软 AI Agent 入门课 client 端连接流程逐步核
- MCP 会话断了怎么续:微软 AI Agent 入门课的 resumable client
- MCP 和 A2A 不是同一个问题:微软 AI Agent 入门课第 11 课