
国庆假期没出远门留在家里把一个折腾很久的想法落地了让队友在群聊里 一台电脑就能让它本地的 Claude Code 出来干活。现在效果大概是这样的——你往群里丢一句“dev-01 帮我把测试失败的那个用例定位一下”过几分钟这台电脑上的 Claude Code 会自己打开仓库、分析问题、改代码甚至跑测试然后把结果整理好发回群里。整个过程不需要 SSH不需要远程桌面只用大家天天在用的群聊界面。把这个项目拆开看核心其实是给 Claude Code 套了一层消息总线把聊天消息变成它的输入把它的输出再变成聊天消息。真正接进来之后我发现这个思路比传统远程开发有意思得多也更适合小团队内部协作。1. 项目整体构想与方案选型1.1 为什么不做 SSH/远程桌面偏要绕一圈走群聊先说最直接的需求很多时候我人在外面脑子里冒出一个小想法想赶紧让电脑上的 Claude Code 帮忙跑一下。手机开 SSH 不是不行但终端小、操作费劲还要记地址和密钥远程桌面更不用说画质差、延迟高为了敲一句话还要等半分钟画面加载。更重要的是这种临时任务往往不是我一个人用团队成员也可能在群里喊一句“帮我跑个脚本”总不能把服务器账号密码发给每个人。群聊天然解决了这些问题聊天窗口本来就是大家每时每刻都盯着的东西 一下就像下了个指令谁发的、什么时候发的、机器最后吐出什么结果所有上下文都留在聊天记录里自动形成一条可追溯的任务流水线。另外聊天工具本身有完善的用户体系谁有权限触发、谁来审计都能在现有框架里解决不用自己做一套用户登录。从产品视角看这其实是把“代码执行”从一个命令行工具重构成了一种“消息驱动协作工具”门槛一下子低了很多。1.2 群聊平台怎么挑要内网可控不要公网依赖做这个项目之前我先把可能用的聊天平台列了一遍对比下来发现选择其实不多。方案接入成本可自托管是否需要公网回调适合场景Rocket.Chat中要跑服务是否小团队/局域网完全私有企业微信群机器人低否是已有企业微信接受云平台中转钉钉自定义机器人低否是已有钉钉接受云平台中转Telegram Bot低否是依赖外部网络不适合纯内网自写Web聊天室高要写前端是否纯局域网快速验证最终我选了 Rocket.Chat。原因很实在它完全开源、可以自托管机器人能用 WebSocket 直接收发消息不需要任何公网回调地址。部署也不需要额外弄一台服务器直接跑在开发机上就行MongoDB 和 Rocket.Chat 本体用 Docker Compose 拉起局域网内其他队友把网页一开就能进群。如果团队已经有企业微信或者钉钉其实也可以走群机器人方案核心路由逻辑完全一样只是消息入口的接入方式不同。这个项目最该关注的不是聊天平台本身而是平台把消息事件暴露给我们的方式是否足够灵活。1.3 组件拆分与消息流整个系统拆成三层接入层、路由与队列层、执行层。接入层是一个常驻 Node 进程负责订阅群聊消息事件路由与队列层从消息里解析出 指令查白名单把任务塞进 FIFO 队列执行层调用 Claude Code 的命令行接口收集输出再把结果回传到群聊。消息流是群内消息 - Bot Listener - 指令解析 - 队列 - Claude Code 子进程 - 结果格式化 - 群内回复。刚开始我以为只要把收到的消息直接传给 Claude Code 就行后来发现不行。Claude Code 不是毫秒级 API一个任务动辄几十秒甚至几分钟而且它背后就是一个真实的工作目录多个任务同时跑很容易互相踩踏比如同时改同一个文件、同时执行测试命令最后两个任务都失败。加上队列以后大家发任务就像排队进试衣间虽然会等但至少不会乱。这个设计是整套系统稳定性的关键。2. Claude Code 环境准备与核心配置2.1 安装一条 npm 命令搞定在继续之前得确保目标机器上有 Node.js 18 以上的环境。安装 Claude Code 的命令很简单npm install -g anthropic-ai/claude-code装完以后跑一下claude --version能输出版本号就说明装好了。如果不想全局安装也可以用npx anthropic-ai/claude-code但在机器人场景下每次拉起都经过 npx 会多几秒解析时间我建议还是全局安装更省心。装完之后建议先手动执行一次claude走一遍初始化流程。这一步不是为了体验终端对话而是让它在用户目录里生成好配置、完成登录、把依赖检查一遍。很多环境问题在这个阶段就会暴露出来比接好机器人之后再去排查要舒服得多。2.2 别让 npm 权限坑了你auto-update 失败很多人装完 Claude Code 之后会遇到这样一行报错auto-update failed: no write permission to npm prefix这个问题的本质不是 Claude Code 没装好而是 npm 全局包的目录权限不对。Claude Code 启动的时候会尝试自动更新更新时要往全局目录写文件如果全局目录在/usr/lib/node_modules或者/usr/local/lib/node_modules这类系统路径下普通用户根本没有写权限更新自然就失败了。解决办法是把 npm 全局目录挪到用户目录下npm config set prefix ~/.npm-global export PATH$HOME/.npm-global/bin:$PATH echo export PATH$HOME/.npm-global/bin:$PATH ~/.bashrc npm install -g anthropic-ai/claude-code这样做有三个好处不需要 sudo 就能装全局包Claude Code 自动更新不会报错后续装其他命令行工具也顺带解决了权限问题。如果你用的是 nvm 管理 Node 版本npm 全局目录天然就在用户目录下基本遇不到这个坑。2.3 登录方式与 API Key 管理Claude Code 支持两种常见认证方式一种是交互式登录首次执行claude时在终端里完成 OAuth 流程另一种是用环境变量注入 API Keyexport ANTHROPIC_API_KEYsk-ant-xxxx交给机器人执行的时候我更推荐用环境变量方式。因为机器人是无人值守的没有人有机会去终端里点“登录”只要在系统环境变量里放好 Key子进程调用 Claude Code 时就会自动读取多台机器也能各自配各自的 Key。如果你的团队有兼容 Anthropic 协议的自建模型网关还可以通过ANTHROPIC_BASE_URL环境变量把它指到对应地址。有一点要单独强调永远不要在群聊消息里贴 API Key也永远别把环境变量内容打进日志。Key 的管理要像对待数据库密码一样谁用谁知道别出现在任何共享渠道里。2.4 非交互模式机器人的发动机平时我们用 Claude Code 都是终端交互模式但机器人没有办法模拟人类对话必须靠它的 print 模式。核心命令长这样claude -p 用Python写一个快速排序并给出测试用例 --output-format text加上--output-format json可以拿到结构化结果方便后续解析。print 模式的特点是执行完就退出不会等待输入非常适合封装在脚本里调用。在机器人场景下需要注意一点Claude Code 如果要执行写文件、跑命令这类操作默认会在关键步骤前等待确认但机器人没有键盘可以按回车。解决办法有两类一类是通过配置把命令白名单提前写好一类是直接在受信机器上加--dangerously-skip-permissions参数。我的建议是只在隔离的开发目录里使用后者生产机器不要开。这个参数组合是整套系统的发动机后面所有任务本质上都是在拼 prompt然后调用这一条命令把结果拿回来。3. 群聊机器人核心实现3.1 监听群聊消息先把事件接到手里在 Rocket.Chat 的后台创建一个机器人用户拿到认证 Token然后写一个 Node 进程订阅消息事件。由于各聊天平台的事件回调名字不太一样我不打算贴完整的 SDK 接入代码因为官方 API 经常变动核心其实是一个回调函数每次群里来消息把消息文本、发送者用户名、房间 ID 交给我们自己写的 handleMessage。你只要把这个函数绑定到聊天工具的事件上后面的解析逻辑完全通用。async function handleMessage(message, meta) { const text message.text || ; const sender meta.user.username; const roomId meta.roomId; if (!text.includes(claude)) return; if (!WHITELIST.has(sender)) { await send(roomId, ${sender} 你不是白名单用户我没法帮你跑任务。); return; } const parsed parseCommand(text); if (!parsed) { await send(roomId, ${sender} 格式不对标准姿势是claude [目标机器] 任务描述); return; } enqueue({ ...parsed, sender, roomId }); await send(roomId, ${sender} 收到任务已排队。我会在结束后把结果发到这里。); }这个函数把消息入口做得足够薄识别指令、校验权限、解析任务、丢进队列。其他复杂逻辑都不要放在这里不然以后想换聊天平台会很痛苦。3.2 解析与路由怎么识别“叫的是谁”项目里我支持两种 claude叫机器人本体dev-01这种目标机器别名是为了指定哪台电脑执行。解析逻辑用一行正则就能搞定function parseCommand(text) { const m text.match(/claude\s(?:(\w))?\s([\s\S]*)/i); if (!m) return null; const target m[1] || local; const prompt m[2].trim(); if (!prompt) return null; return { target, prompt }; }接着在配置里维护一张机器别名表const HOSTS { dev-01: { cwd: /srv/projects/alpha, apiKey: process.env.KEY_DEV01 }, mac-tower: { cwd: /Users/build/workspace, apiKey: process.env.KEY_MAC } };解析结果是一个 job 对象包含目标机器、prompt、发送者和房间 ID。这样做等于提前为多主机留好了口子当前机器人跑在哪台机器上就执行哪台机器的任务目标不在本机时可以直接拒绝或者由中央调度器做二次转发。实际用下来这个“每台机器一个机器人”的模型最省心任务数据、仓库克隆、权限都以本机为基础没有跨机器传文件的麻烦。3.3 队列与子进程别让 Claude Code 被消息淹没任务进来以后我用一个简单的队列串行执行let queue []; let running false; function enqueue(job) { job.id queue.idCounter || 1; queue.push(job); runNext(); } async function runNext() { if (running || queue.length 0) return; running true; const job queue.shift(); try { const output await execClaude(job); await send(job.roomId, formatResult(job, output)); } catch (err) { await send(job.roomId, 任务 #${job.id} 执行失败${err.message}); } finally { running false; runNext(); } }真正的执行函数用 Node 的 child_process.spawn 拉起 Claude Codeconst { spawn } require(child_process); function execClaude(job) { return new Promise((resolve, reject) { const host HOSTS[job.target] || HOSTS[local]; const child spawn(claude, [-p, job.prompt, --output-format, text], { cwd: host.cwd, env: { ...process.env, ANTHROPIC_API_KEY: host.apiKey }, shell: false }); let stdout ; let stderr ; child.stdout.on(data, d (stdout d.toString())); child.stderr.on(data, d (stderr d.toString())); const timer setTimeout(() child.kill(SIGKILL), 10 * 60 * 1000); child.on(close, (code) { clearTimeout(timer); if (code 0) resolve(stdout.trim()); else reject(new Error(退出码 ${code}: ${stderr || stdout})); }); }); }这里有两个细节值得展开。第一我选 spawn 而不是 exec是因为 exec 会把所有输出攒到缓冲区遇到超长结果容易爆内存spawn 可以流式接收对长时间运行的 AI 任务更稳。第二环境变量里通过ANTHROPIC_API_KEY注入密钥干掉了交互登录的负担也让每台机器可以用不同的 Key 指向不同账号。超时定时器也是必须的万一 Claude Code 卡在某个死循环10 分钟后无条件杀掉机器人是无人值守的绝对不能无限等。3.4 结果回传与输出控制Claude Code 返回的通常是 Markdown 和代码片段群聊渲染不一定好看。我做了两层处理长度超过 3000 字符就截断并在末尾提示“完整结果请让管理员查看日志”格式上把代码围栏、粗体和标题符号做一次轻量转换让它更接近普通聊天文本方便在手机上看。如果任务耗时长我还加了心跳消息机制每过 30 秒往群里发一条“任务 #xx 仍在执行中已用时 x 分钟”避免大家以为机器人死掉了。这个功能一开始我觉得没必要觉得只会刷屏后来实际跑起来才发现群里没有人知道一个任务正常需要多久如果两分钟没回话就会有人问“是不是坏了”。加了心跳之后整个体验安静了很多这是我自己比较满意的一个细节。4. 实操日志、排坑与安全补丁4.1 一次完整任务回放我挑一个真实测试的例子。群聊里有人发了claude dev-01 用Python写一个脚本扫描当前目录下24小时内修改过的文件按文件大小降序输出前20个保存到 /tmp/recent.txt机器人先回一句收到任务已排队 #17。我会在结束后把结果发到这里。大概两分钟后群里收到了 Claude Code 的产物脚本路径、运行结果摘要、完成标记。整个过程没有人打开终端也不需要谁坐回电脑前。我统计了一下国庆期间几类任务的耗时任务类型平均耗时备注解释代码片段8~15秒最快生成脚本并运行1~3分钟涉及写文件和跑命令修复测试失败3~8分钟需要反复读日志和改代码大仓库分析10分钟以上建议拆分 prompt这些数字说明群聊 一下虽然方便但也不是毫秒级魔法适合结果可以异步等待的场景。如果你要处理的是紧急线上事故还是老老实实 SSH 上去自己动手更靠谱。4.2 高频报错速查表做这个项目踩过的坑按出现频率排一下症状原因解决办法auto-update failed: no write permission to npm prefixnpm 全局目录没写权限按上文方法把 prefix 改到用户目录任务一直停在 waiting for input非交互模式没跳过权限确认配置工具调用的允许规则受信环境加--dangerously-skip-permissions返回 401/403API Key 过期或失效更新环境变量重启机器人进程输出全是 markdown 代码围栏聊天端不渲染 Markdown发送前转纯文本或切片WebSocket 掉线后机器人假死长连接没有重连机制给监听端加心跳和自动重连任务太长被聊天端截断单条消息超过平台限制超过 2000 字符自动分多条发送第一个问题在安装阶段最容易遇到很多人以为 Claude Code 没装好其实和它没关系就是 Node 环境的权限不规范。把它修好后Claude Code 的自动更新会顺畅很多也不影响后续其他 npm 全局工具。4.3 安全补丁这东西本质是个远程执行器把 Claude Code 接到群聊里等于给群聊里的人发了一张能跑代码的通行证。虽然方便但一旦被滥用就是灾难。我做了几层收敛只监听内网Rocket.Chat 不开放公网注册机器人进程只绑在内网 IP 上不要在公网服务器上裸奔。白名单约束不是群里所有人都能触发任务我把能触发的人写到配置里未命中直接拒绝。低权限运行机器人用独立系统用户跑工作目录只用项目目录的读写权不给整个系统的权限。密钥隔离API Key 从环境变量读取不落盘、不打印、不进群聊。日志审计每一次任务是谁、什么时候、发了什么 prompt、返回什么结果全部留日志出了问题可以回溯。我还强烈建议把 Claude Code 的工作目录限定在一个沙箱目录里。你可以理解为它拿到了这个房间的钥匙但打不开整栋楼的其它房间。这样即使 prompt 里有恶意指令破坏面也被限制住了。如果对隔离要求更高还可以用 Docker 容器包一层把宿主机文件系统彻底隔开。4.4 多主机与扩展方向这套结构天然支持多台电脑最简单的做法是每台目标电脑各自跑一个 bot 进程所有人都能用同一个群只有消息里点名的机器会响应。这种“边车模式”的好处是任务数据、仓库、权限都在本机上不存在跨机器传文件的麻烦。真正的中央调度器反而少因为大部分协作场景都是“谁方便谁跑”而不是某台服务器统一派发。我后续还想做几个小功能结果附带 diff 预览让群里直接看到改了什么定时巡检任务比如每天早上让 Claude Code 把几个仓库的测试状态发进群再加一个任务取消指令发“取消 #17”就能终止还在跑的任务。这些都很实用但不要一次全上先把稳定性和安全底子打好最重要。做这个项目最大的收获不是“远程编程有多酷”而是把“聊天即控制界面”这个思路完整地走通了一遍。最初版本傻乎乎地把每个结果都刷屏后来才知道队列和截断输出才是体验的关键。现在这套小系统还在电脑上跑着偶尔群里有人喊一声代码就自动跑起来了像多了一个沉默的运维同事。如果你也想给团队做类似的东西建议从小范围、受信环境开始先把一条任务跑通再逐步加机器加功能。