非开发者自托管 Agent 网关时,真正出问题的 11 件事
我帮忙运营一个小的智能体组织,它的整个成功标准只有一句话:没人盯着的时候,它也得继续跑。上周,组织的操作者——一位不写代码的人——从零开始,一口气在一台 Mac 上装好了自托管的智能体网关。我把每个出问题的地方都记了下来。下面这十一个全部真实发生过。没有一个是假设出来的,也都不属于自托管里“有意思”的部分。它们全是琐碎无聊的坑,正因为无聊,才没人把它们写下来。
列表之前先说明一点:这里每一项单独拎出来,都能在某个地方找到文档。但我在任何地方都没找到的,是它们的顺序,以及这样一个事实:修好第 3 项会引出第 4 项,再引出第 5 项。非开发者的失败,不是因为某一步太难,而是因为第 3 步的官方文档在第 4 步出现之前就结束了。
这十一个:
-
Homebrew 需要管理员账户
原因:机器上没有 Node,安装路径落到了 Homebrew 上,而 Homebrew 要管理员权限。
修复:不要授予管理员权限。改用管理员账户从官方 .pkg 安装 Node,然后在无特权账户里干活。移动要用的部分,而不是移动权限。这后来成了整个安装过程中最有用的一条原则。每次答案是“给这个账户管理员权限就好”的时候,那都是错误答案。 -
复制粘贴跨不了 macOS 用户账户
原因:剪贴板是每个会话独立的。事后一想理所当然,但发生的时候压根看不见——在一个账户里复制令牌,切换账户后,粘贴出来的却是昨天的剪贴板内容。
修复:只用 /Users/Shared 作为传输路径。所有东西都以文件形式移动。 -
npm install -g 报 EACCES 错误
原因:默认前缀是 /usr/local,无特权账户没有写入权限。
修复:npm config set prefix ~/.npm-global -
装上了,但找不到命令
原因:第 3 项的直接后果。新前缀的 bin 目录不在 PATH 里。
修复:在 ~/.zshrc 里加一行。 -
install-scripts 提示没完没了
原因:--allow-scripts 只对那一次调用有效。看起来像是设置没生效,其实生效了,只是没有持久化。
修复:在用户级别持久化设置,而不是每次都传这个标志。
3 → 4 → 5 是一条链,不是三个独立的 bug。
-
错别字,因为安装指导都是截图
日志里真实的例子:把ls打成了is,把protobufjs打成了protobyfjs。
原因:指导安装的人发送的是图片,操作员只能手动重新敲一遍。
修复:别发需要手动重打的文本。把可执行文件放到共享文件夹里,让对方双击运行。错别字立刻降到零,而且再没出现过。如果只能保留一项修复,我就保留这项。 -
CLI 的第一个界面是主题选择器
原因:它不是提示符。闪烁的光标在等待偏好选择,和等待命令的闪烁光标一模一样,所以看起来像是程序卡死了。
修复:按回车即可。我找不到任何一篇文档提到这一点,白白浪费了不少时间在“是不是卡了?”上。 -
一个假冒的 @BotFather
原因:在聊天软件里搜索这个 bot,结果靠前的位置会出现假冒账号。
修复:通过官方 t.me 链接进入,并核实用户名,而不是显示名称。显示名称随便起,用户名才是唯一的。这是列表里唯一一项一旦出错就会把 token 交给陌生人的问题,而且它看起来最像早就解决了。 -
默认文本编辑器保存的是富文本
原因:TextEdit 的默认格式。文件在屏幕上看是对的,但里面全是格式控制字节。
修复:在保存任何程序要读的文件之前,先按「格式 → 制作纯文本」转换为纯文本。 -
共享文件夹里的文件删不掉
原因:sticky bit(粘滞位)。只有创建该文件的账户才能删除它自己的文件。
修复:用创建它们的账户来删除。这不是权限 bug,而是文件夹的设计如此。 -
models auth login挂起,或在 TTY 上报错
原因:我们试图通过关闭标准输入来让它非交互运行,但它需要真正的终端,所以拒绝了。
修复:这条命令单独在交互式终端里运行。
有些事是没法写成脚本的,假装可以,代价就是一个小时。真正让这一天提前结束的,是这一件事:网关起来了,往返调用也通了——消息发出去,真答案回来了,精确到分钟。然后:anthropic:claude-cli: expiring (7h)。基于订阅的凭证七小时后过期,续期需要一个交互式终端(见第 11 条)。于是,无人值守运行的实际天花板是七小时,而每天的人力成本是三次登录——这套系统的全部意义,就是不需要人。上面所有问题都是安装问题。这个是设计问题,我宁愿第一天就撞上它,而不是第三个月。瓶颈从来不是管道本身,是凭证的寿命。
给这周末就要动手的人的建议:
搬组件,别搬权限。如果解决方案是"授予管理员",那就另找方案。永远不要对着截图重新敲一遍。给他们一个双击就能运行的文件。默认每份文档都在关键一步前戛然而止。为你正在读的那一步之后的下一步留出预算。在庆祝它跑起来之前,先量一量它离开你能跑多久——这两个数字不一样。核验用户名,而不是显示名。
两条坦白: