同步脚本的最后一个步骤通过时,页面上出现了一排绿色的检查。我原本以为这件事已经结束了:上游代码拉下来了,兼容测试过了,生产构建也过了,生成提交已经推回主分支。
Vercel 很快给了另一个答案。它没有把新版本发布出去。
第一排绿色只负责它检查的部分
这次同步之前,失败已经发生过一次。生成工作树提交时,上游安装进来的 lint-staged 钩子会尝试自动 stash;镜像工作区没有适合它保存的状态,于是失败停在了 Commit and push。修复的范围很小:同步脚本自己的提交不再运行仓库作者的钩子,应用代码和测试仍然留在前面。
修完以后,Run 36310785008 从抓取上游开始一路通过。它证明了几件很具体的事:上游提交是 bb6ac766e555,依赖能够安装,fork 保留的兼容测试能够通过,生产构建能够完成,最后的生成提交也能推回 main。它没有证明 Vercel 能用同一棵树完成部署,更没有证明首页、文章详情和 RSS 都已经换成了这次构建。
以前我会把这几种结果看成一张总成绩单。现在更愿意把它们拆成几条窄窄的答案:代码能不能装,测试覆盖到的行为有没有变化,构建产物能不能生成,代码能不能推上去,部署平台能不能把产物接住。它们之间有先后关系,但没有谁能代替谁。
失败停在复制设计系统的地方
GitHub 的 Vercel 状态变红以后,我先看部署日志,没有重跑同步脚本。日志把项目根目录、提交和失败命令都写得很清楚:Vercel 的项目根是 apps/web,安装命令会回到工作区根目录,执行 git submodule update,然后把 design-oss/design-system 复制到 packages/design-system。
问题在于,Vercel 构建环境里留下了一个空的子模块目录,却没有把设计系统的内容初始化进去。复制命令找不到源目录,构建还没有进入 Next.js,就已经结束了。站点的两个旧页面仍然可以返回 200,所以从浏览器看起来一切正常;新提交其实没有成为生产版本。
这是一种很容易被忽略的失败:应用代码没有报错,API 也没有报错,只有构建环境和仓库里一个不显眼的链接不在同一个状态。
第一次修复还不够
我把 Vercel 项目的安装命令改成了一个明确的兜底流程:如果 design-oss/design-system 不存在,就浅克隆公开的 Yohaku 仓库;随后复制设计系统,再安装工作区依赖。重新部署时,子模块问题确实消失了,但日志紧接着出现了第二个错误。
工作区的 pnpm-lock.yaml 没有 packages/design-system 这一 importer。设计系统原本来自子模块,主仓库的锁文件并没有为它留下完整入口;复制目录以后,Vercel 的默认冻结安装反而拒绝继续。这个错误比第一次更有价值,因为它让我看见了修复的代价:想让构建环境补齐一个运行时目录,就必须同时承认锁文件并没有描述它。
当前兜底使用 pnpm install --no-frozen-lockfile,让构建机根据工作区实际目录补全安装。它不是最理想的长期供应链方案,构建的可重复性比严格冻结安装弱一些;但它把缺失的前置条件写进了命令,而不是继续假设 Vercel 会替我们初始化子模块。后续如果上游把设计系统纳入可冻结的仓库结构,应该再把这个临时兼容收回去。
第二次部署最终完成,Vercel 的构建日志显示依赖安装、Next.js 构建和产物发布都结束,km.caiths.com 被指向新的生产别名。随后两个自定义域都返回 200,响应体大小和 ETag 也发生了变化。这些证据足以说明这次版本已经被接住,但还不等于整个博客的每个页面都经过了人工回归。
速度快了,阅读不能省掉
备忘录里有一段很早的感受:从简单对话框到 Coding Agent,代码生成、执行和测试的速度变快了,自己却越来越难跟上。以前我把这理解成工具改变了写代码的乐趣。今天排查 Vercel 时,它又出现了另一种形状。
如果我只盯着同步 Run,任务会在“绿色”那里结束;如果我只盯着域名的 200,任务会在“页面还能打开”那里结束。真正的工作发生在两个状态之间:看日志,确认环境,承认第一次修复暴露了第二个缺口,再决定这次临时放宽锁文件到底承担什么代价。
这几步没有让代码变得更快,反而让我在一条已经通过的流水线前停久了一点。速度适合用来缩短重复动作,不能用来省略阅读。尤其是部署这种跨系统的事情,源码、CI、构建平台、缓存和域名各自只知道一小段,任何一段绿色都不应该替整条链路发言。
我现在会把“部署完成”写成一组证据,而不是一个动词:同步 Run 的上游 SHA 和生成提交,Vercel 的构建日志,生产别名,两个自定义域的响应,以及需要人工打开的关键页面。哪一项还没有答案,就继续停在“已验证到这里”。
这次没有改博客文章,也没有碰后端数据。只是修了一条同步链路,却重新确认了一件很慢的事:当工具替我把事情做得越来越快,我更需要给自己留出把结果读完的时间。