背景:用微信操控 Claude Code
cc-wechat 是一个社区开发的 MCP Server 插件,它通过腾讯的 iLink Bot API 打通了微信和 Claude Code 之间的消息通道。安装后只需微信扫码登录,就能随时随地通过微信向 Claude Code 发送指令、接收回复——不用打开电脑,不用 SSH,用手机就能让 Claude 帮你改代码、查日志、跑部署。
微信 → 腾讯 iLink API → MCP Server (long-poll) → Claude Code
← sendMessage ← reply 工具
听起来很美好。但实际用起来,很快就会发现几个让人抓狂的问题。
三个痛点
痛点一:发完消息石沉大海
微信发出消息后,Claude Code 接收到了,开始思考、调用工具、读文件、跑命令……但这一切都只发生在终端里。微信那边呢?一片沉默。你不知道消息到了没有,不知道 Claude 在干什么,不知道要等多久。尤其是复杂任务可能需要几分钟,这种"已读不回"的体验令人焦虑。
痛点二:回复发送失败,内容被吞
Claude Code 处理完任务后,通过 mcp__wechat-channel__reply 工具向微信发送回复。但偶尔(尤其是长回复时),模型会在生成工具调用参数时混入 XML 语法片段,比如把 context_token 的值后面接上 </context_token><parameter name="content">...,导致参数解析失败。结果就是——Claude 明明处理完了,你却收不到回复。

reply-fix.log 中记录的修复前后对比:before 行可以看到 context_token 尾部被 XML 片段污染,after 行是清洗后的正确参数
痛点三:用量耗尽,彻底失联
当 Claude Code 的 plan 用量达到上限、或因 max_tokens 等原因异常停止时,它只是在终端输出一条错误信息然后退出。但微信用户看到的还是之前的"处理中..."(如果有即时确认的话)或者根本没有任何消息,之后就再也不会收到回复了。
解决方案:Claude Code Hooks
Claude Code 提供了一套 Hooks 机制,允许在特定生命周期事件触发时执行自定义脚本。我们用到了三个 Hook 事件:
| Hook 事件 | 触发时机 | 用途 |
|---|---|---|
UserPromptSubmit |
用户消息提交时 | 即时确认 |
PreToolUse |
工具调用执行前 | 参数修复 |
Stop |
Claude 停止响应时 | 异常通知 |
此外还需要一个辅助 Hook:
| PostToolUse | reply 工具执行后 | 标记已回复 |
下面逐一说明每个 Hook 的实现思路。
Hook 1:即时确认(wechat-ack.sh)
事件:UserPromptSubmit——每当有消息进入 Claude Code 时触发。
逻辑:
- 检测消息是否来自微信(通过
<channel source="wechat-channel">标签) - 解析
user_id、context_token等参数 - 通过 iLink API 立即发送"收到,处理中..."
- 保存会话状态到
/tmp/wechat-hooks/供其他 Hook 使用 - 注入系统消息,提醒 Claude 处理完成后通过 reply 工具回复
额外的,脚本还实现了三个斜杠命令:
/new → 开始新话题(注入系统消息,让 Claude 不引用之前的上下文)
/help → 显示帮助信息
/status → 查看 Claude Code 运行状态
核心的消息发送逻辑很简单,就是拼 JSON 调 iLink 的 sendmessage 接口:
send_wechat_msg() {
local text="$1"
local body
body=$(jq -n \
--arg to "$USER_ID" \
--arg ctx "$CONTEXT_TOKEN" \
--arg txt "$text" \
'{msg: {to_user_id: $to, message_type: 2, message_state: 2,
item_list: [{type: 1, text_item: {text: $txt}}],
context_token: $ctx},
base_info: {channel_version: "0.1.0"}}')
curl -s --max-time 5 \
-X POST "${BASE_URL}/ilink/bot/sendmessage" \
-H "Authorization: Bearer ${TOKEN}" \
-d "$body" >/dev/null 2>&1 || true
}

发送消息后立即收到"收到,处理中..."确认,处理完成后收到完整回复
Hook 2:参数修复(wechat-reply-fix.sh)
事件:PreToolUse,匹配 mcp__wechat-channel__reply 工具。
这个问题的根源是模型在生成 JSON 工具参数时偶尔"走神",切换到了 XML 函数调用语法。典型的污染长这样:
context_token: "AARzJW...正常值...</context_token><parameter name="content">这里才是真正的回复内容</parameter></invoke>"
content: ""(空的,因为内容被塞进了 context_token 里)
修复脚本的处理策略:
- 清洗 token:在
user_id和context_token的值中,找到第一个 XML 标签位置并截断 - 提取 content:从被污染的字符串中用正则提取
<parameter name="content">...正文...</parameter>中的内容 - 清理尾部:去掉 content 末尾可能残留的
</parameter></invoke>片段
整个修复过程用 jq 的纯 JSON 处理完成,不需要外部依赖:
FIXED=$(echo "$TOOL_INPUT" | jq -c '
# 截断到第一个 XML 污染标记
def clean_token:
[index("</context_token>"), index("</parameter>"),
index("<parameter name="), index("</invoke>")]
| map(select(. != null))
| if length == 0 then . else .[0:min] end;
# 从污染字符串中提取正文
def extract_param($raw; $pname):
($raw | capture("<parameter name=\"" + $pname
+ "\">(?<c>[\\s\\S]*?)</parameter>").c);
...
')
每次修复都会记录到 /tmp/wechat-hooks/reply-fix.log,方便排查问题。
Hook 3:异常停止通知(wechat-stop-notify.sh)
事件:Stop——Claude Code 停止响应时触发。
逻辑:
- 检查停止原因:如果是
end_turn(正常结束),不做任何事 - 读取之前保存的微信会话状态(由 wechat-ack.sh 写入)
- 根据原因生成对应的通知消息
- 通过 iLink API 发送到微信
case "$REASON" in
max_tokens)
MSG="⚠️ 回复因长度限制被截断,请稍后重试或缩小问题范围。"
;;
*)
MSG="⚠️ 处理中断(原因:${REASON:-未知}),可能是用量已达上限。请稍后再试。"
;;
esac
但这里有一个问题:Stop 事件在 Claude 每次停止时都会触发,包括正常结束。如果不区分,用户在正常对话中也会收到"处理中断"的误报。
解决方案是配合一个 PostToolUse hook(wechat-reply-sent.sh):当 reply 工具成功执行后,创建一个 .replied 标记文件。Stop hook 检测到标记存在就跳过通知,只有在没有成功回复的情况下才发送异常通知:
# Stop hook 中的检查逻辑
REPLIED_MARKER="/tmp/wechat-hooks/${SESSION_ID}.replied"
if [[ -f "$REPLIED_MARKER" ]]; then
rm -f "$SESSION_FILE" "$REPLIED_MARKER"
exit 0 # 已回复,无需通知
fi
这样即使 Claude Code 因为 plan 用量耗尽而停止,微信用户也能及时知道发生了什么,不会陷入无限等待。
三个 Hook 的协作流程
微信用户发送消息
│
▼
UserPromptSubmit hook (wechat-ack.sh)
│── 即时发送"收到,处理中..."
│── 保存 user_id / context_token 到 /tmp/
│── 注入系统消息
▼
Claude Code 处理任务...
│
├─ 正常完成 → 调用 reply 工具
│ │
│ PreToolUse hook (wechat-reply-fix.sh)
│ │── 检查参数是否被 XML 污染
│ │── 如有则修复
│ ▼
│ 发送回复到微信
│ │
│ PostToolUse hook (wechat-reply-sent.sh)
│ │── 创建 .replied 标记
│ ▼
│ Stop hook → 检测到标记 → 跳过通知
│
└─ 异常停止(无 .replied 标记)
│
Stop hook (wechat-stop-notify.sh)
│── 读取保存的会话状态
▼
发送异常通知到微信
安装与使用
前提条件
- Claude Code v2.1.80+
- cc-wechat 已安装并扫码登录
- jq 命令行工具
下载安装包
cc-wechat-hooks.tar.gz(约 6KB)
一键安装
tar -xzf cc-wechat-hooks.tar.gz
cd cc-wechat-hooks
bash install.sh
安装脚本会自动:
- 复制三个 hook 脚本到
~/.claude/hooks/ - 备份现有的
settings.json - 将 hook 配置合并到
~/.claude/settings.json
手动安装
如果你偏好手动操作:
# 1. 复制脚本
cp hooks/wechat-*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/wechat-*.sh
# 2. 编辑 ~/.claude/settings.json,在 hooks 字段中添加:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__wechat-channel__reply",
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/wechat-reply-fix.sh",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "mcp__wechat-channel__reply",
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/wechat-reply-sent.sh",
"timeout": 5
}
]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/wechat-stop-notify.sh",
"timeout": 10
}
]
}
],
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/wechat-ack.sh",
"timeout": 10
}
]
}
]
}
}
验证安装
安装后启动 Claude Code:
claude --dangerously-skip-permissions --dangerously-load-development-channels server:wechat-channel
必须要加--dangerously-skip-permissions,不然每次简单的权限操作都会阻塞而停下来。
用微信发一条消息,应该会立即收到"收到,处理中..."的确认回复。
开发过程回顾
这三个 Hook 并不是一次性设计出来的,而是在实际使用 cc-wechat 的过程中,随着问题的暴露逐步迭代添加的。
第一步:即时确认。安装 cc-wechat 后第一个发现的问题就是没有反馈。Claude Code 在终端里忙得热火朝天,微信那边却一片死寂。UserPromptSubmit hook 是最直接的解决方案——消息进来的第一时间就回复,不需要等 Claude 处理完。
第二步:参数修复。用了一段时间后,发现偶尔收不到回复。查看终端发现 reply 工具调用报错,参数格式不对。分析日志后定位到是模型在长回复时会"漂移"到 XML 语法。写了 PreToolUse hook 来自动修复,并保留日志方便后续观察问题频率。
第三步:异常停止通知。有一次等了很久没回复,去看终端才发现是 plan 用量到了上限,Claude Code 早就停了。这种情况下前两个 Hook 都帮不上忙——消息确认发了,但后续处理根本没开始或中途中断了。于是加了 Stop hook 来兜底。
第四步:修复误报。Stop hook 上线后发现每次正常对话结束也会发通知——用户收到的是这样的消息:

正常对话结束后也收到了"处理中断"通知,原因显示为"未知"
原因是 Stop 事件的 reason 字段在正常结束时并不是预期的 end_turn,而是空值。单纯靠 reason 判断行不通。解决办法是加一个 PostToolUse hook:reply 工具成功执行后创建标记文件,Stop hook 看到标记就跳过通知。四个 Hook 形成了一条完整的链路。
每个 Hook 都是解决一个具体的、在实际使用中发现的问题。没有过度设计,够用就好。
已知限制
- 权限审批:Claude Code 的权限确认弹窗仍然只能在终端操作,微信端无法审批。建议提前在
settings.json中配置好常用工具的allow权限。 - Session 过期:iLink 的登录 session 会过期,需要定期重新扫码(
npx cc-wechat login)。 - 单向通知:Stop hook 只能发送文字通知,无法在微信端重启 Claude Code。
总结
cc-wechat 提供了基础的微信-Claude Code 通信能力,而 Hooks 机制让我们能在不修改 cc-wechat 源码的情况下补全体验短板。三个脚本加起来不到 200 行 bash,却覆盖了消息确认、错误修复、异常通知三个关键场景,在短暂离开电脑,或是需要实时关注Claude的执行状态时,变得基本可用。 但毕竟它只支持单Session,也无法进行清空context,权限审批等操作,直接做为“个人助手”还是先天不足。好在本身MCP的SIZE很小,也就占用200左右的token,Hook也不增加上下文和token的负担。做为一个轻量替代案还是比较好用的。
如果你也在用 cc-wechat,欢迎下载试用。有问题或改进建议可以在评论区交流。
评论