下拉查看上一篇

用三个 Hook 脚本让 cc-wechat 真正可用——Claude Code 微信交互体验增强实录


背景:用微信操控 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 修复日志

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 时触发。

逻辑:

  1. 检测消息是否来自微信(通过 <channel source="wechat-channel"> 标签)
  2. 解析 user_id、context_token 等参数
  3. 通过 iLink API 立即发送"收到,处理中..."
  4. 保存会话状态到 /tmp/wechat-hooks/ 供其他 Hook 使用
  5. 注入系统消息,提醒 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 里)

修复脚本的处理策略:

  1. 清洗 token:在 user_id 和 context_token 的值中,找到第一个 XML 标签位置并截断
  2. 提取 content:从被污染的字符串中用正则提取 <parameter name="content">...正文...</parameter> 中的内容
  3. 清理尾部:去掉 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 停止响应时触发。

逻辑:

  1. 检查停止原因:如果是 end_turn(正常结束),不做任何事
  2. 读取之前保存的微信会话状态(由 wechat-ack.sh 写入)
  3. 根据原因生成对应的通知消息
  4. 通过 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

安装脚本会自动:

  1. 复制三个 hook 脚本到 ~/.claude/hooks/
  2. 备份现有的 settings.json
  3. 将 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 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,欢迎下载试用。有问题或改进建议可以在评论区交流。

评论

上滑查看下一篇