你批准了一次工具调用,理所当然地以为它只会跑一次。十月里有五天,某个版本的 Claude Code 在某些会话里把这条默认预期打破了。

打开网易新闻 查看精彩图片

2.1.296 于 10 月 9 日发布。官方更新日志里 10 月 14 日发布的 2.1.299 条目写着:修复了 2.1.296 中的一个回归问题——当会话在重启后恢复时,如果有新消息到达,云端和无头(headless)会话可能会把已批准的工具调用执行两次。

先看清触发条件再紧张:仅限云端和无头会话,且仅限会话重启恢复期间有新消息到达的那一刻。窗口很窄。但想想一次批准背后挂着什么——git statuscode> 跑两遍不花一分钱;一次数据库迁移、一次部署、一条 gh pr create、一次支付接口调用跑两遍,就得靠手工收拾。

没人主动选过 2.1.296。原生安装的 Claude Code 会在后台自动更新,默认通道是 latest,按安装文档的说法,这个通道"新功能一发布就能拿到"。一个每次运行都重新安装 Claude Code 的 CI 任务,拿到的就是那一分钟最新的版本。

一个会自动更新的编码代理,意味着每隔几天就有你没读过的代码,带着你的权限在你的仓库上跑。把它当成一次依赖升级来对待:锁住版本,先让一台机器试每个新版本,其余人再跟进。

九天日志,当成依赖升级来读

双次执行不是那一周唯一值得读的一行。10 月 8 日发布的 2.1.294 条目写着:修复了以指令形式编写的 prompt 和 agent 钩子(例如"阻止那些……的命令")反而放行了本应阻止的操作。

按钩子文档,prompt 钩子把决策交给一个 Claude 模型做单轮评估,agent 钩子则派生一个子代理,可以在决策前读取文件。假设你写了一个钩子说"阻止任何触碰生产数据库的命令"。更新日志列出了 2.1.294 对这类指令式钩子的修复,但没说哪些更早的版本受影响。在受影响的构建上,守卫躺在你的设置文件里,却仍可能把命令放过去。

再看 10 月 8 日到 16 日之间同一份日志里的其他变化:

  • 2.1.295 为命令和 HTTP 钩子增加了 onFailure: "block",钩子无法启动或超时时阻止动作,而不是放行。
  • 2.1.299 修改了插件钩子:当动作无法交给钩子、或钩子无法交回答案时,拒绝该动作。
  • 2.1.302 修改了 claude -p 和 Agent SDK 会话:没有设置权限模式的会话,在"第三方提供商、遥测关闭或全新安装"的情况下以 default 模式启动,并补充说 --permission-mode auto 保持 auto。一个从未设置过模式的无头任务,可能在一次它没选过的更新后行为不同。

这些改动大多让你更安全。夹在中间的 2.1.296,让无头会话变得更不可预测。它们的到达方式都一样:按安装文档,"更新在后台下载并安装,下次启动 Claude Code 时生效"。

自动更新,一次没人审过的部署

你不会让生产服务随便拉取某个库今早发布的任意版本,这正是 lockfile 存在的理由。而你的编码代理握着 shell、你的 git 凭证,往往还有一两个云令牌。可在大多数机器上,它偏偏是那个被允许漂移的二进制。

支持漂移的理由也公平:快。双次执行的修复在回归出现五天后就进了 latest。锁死版本,你也得等修复。所以别冻结,加一段刻意的滞后:一台机器在真实工作上跑新版本,等这台机器安静几天、且有人读过中间的条目后,其余人再动。

Claude Code 已有的几个旋钮

以下都写在安装文档里,按你想要的滞后程度挑一个。

跟随 stable 通道。在 /config 里把自动更新通道改成 stable,或写进 ~/.claude/settings.json。stable 按文档"通常约一周旧,跳过有重大回归的版本"。minimumVersion 设一个自动更新不会跌破的下限:

{"autoUpdatesChannel": "stable", "minimumVersion": "2.1.299"}

关掉更新器。在同一文件的 env 块里加上 DISABLE_AUTOUPDATER,然后运行 claude doctor。文档说 Auto-updates 一行应显示 disabled (set by env: DISABLE_AUTOUPDATER)。这只停掉后台检查,claude update 仍然可用。要堵住包括手动在内的所有更新路径,文档指向 DISABLE_UPDATES。

安装精确版本。原生安装器和 npm 都接受版本号:curl -fsSL https://claude.ai/install.sh | bash -s 2.1.299,或 npm install -g @anthropic-ai/claude-code@2.1.299。锁定安装不等于锁定运行的版本——文档把 npm 全局安装也描述为会自动更新,也没说带版本号的原生安装会关掉更新器。两种方式都要设 DISABLE_AUTOUPDATER,并用 claude doctor 确认。用 Homebrew 安装的话,文档说 claude-code cask 跟随 stable,默认不自动更新。

给团队强制一个范围。在托管设置里,requiredMinimumVersion 和 requiredMaximumVersion 让 Claude Code 在组织允许的版本之外拒绝启动。

然后让漂移发出声音。在 CI 任务顶部或 shell 配置里加几行:

want="2.1.299"
have="$(claude --version | cut -d' ' -f1)"
if [ "$have" != "$want" ]; then echo "claude is $have, pinned to $want" >&2; exit 1; fi

文档显示 claude --version 打印形如 2.1.89 (Claude Code) 的一行,所以第一个字段就是版本号。

npm 的 stable 标签今天在发什么

"用 stable 就完了"听起来像结论,其实不是。10 月 19 日,npm 注册表上 stable 标签指向 2.1.292,latest 指向 2.1.303。这是 npm 的标签,原生安装器的 stable 通道当天发什么,没有核实。

2.1.292 比 2.1.296 的回归更早,所以 stable 跳过了双次执行。它也比 2.1.294 的修复更早——如果你指望以指令形式编写的 prompt 或 agent 钩子来拦截操作,npm 的 stable 构建没有那个修复。更新日志没说哪些版本有该缺陷,所以在那个构建上先测一测这类钩子,再决定信不信它。

通道替你选了一个延迟,它不会替你读条目。那仍然是你的活,而且很短:

  1. 留一台机器在 latest 上,你自己的,或一个做真实但低风险工作的 CI runner。
  2. 其余人锁在你选定的版本上。
  3. 移动锁之前,打开更新日志,读完两个版本之间的每一条。在页面上搜 "hook"、"permission"、"approved"、"headless" 和 "default"。
  4. 等金丝雀机器安静几天、且第三步里没有任何一条碰到你依赖的守卫时,再移动锁。

2.1.296 的回归很窄,Anthropic 五天内修好了,这是这个故事里好的版本。下一个可能更宽,而自动更新会把它送到每一台你没锁住的机器上。

所以老实说:你的 CI 里现在跑的是哪个版本的编码代理?不查你能答上来吗?