AI Agent 把写代码这件事的节奏彻底改了。CI 瓶颈这个词,一年前还很少有人挂在嘴边,现在却成了工程团队周会上最常出现的抱怨。Linear 的一位工程师在官方博客里复盘了自家流水线的改造过程:PR 的等待时间从 6 分钟以上压到 5 分钟出头,单元测试 runner 的耗时差不多砍掉一半。数字听起来不算惊天动地,但背后的判断值得每个用 TypeScript 写业务的团队抄一遍——当提交代码的人从人变成了 Agent,你原来那条流水线的设计前提就已经不成立了。
瓶颈搬家了,从编辑器搬到了流水线
写代码变快之后,等待开始刺眼
过去十年,工程效率的讨论基本围绕"人"展开:编辑器快不快、补全准不准、代码评审要几轮。AI 编码工具普及之后,这些环节的耗时同时塌陷。一个人原本要半天才能写完的改动,现在十几分钟就能出一个能跑通、甚至自带测试的版本。产出上去了,下游却没动。流水线还是那条流水线,队列还是那支队列,等待时间被相对放大——你不再觉得自己在写代码,而是在等一台机器盖章。
Linear 遇到的正是这个局面。Agent 参与之后,PR 的数量和密度都变了,CI 从"顺手跑一下"变成"必须排队"的资源。拖慢交付的往往不是某个特别慢的任务,而是每一个 PR 都要重新排一次队。
先测,再改:6 分钟里谁在吃时间
改造的第一步不是优化,是把时间拆开。6 分钟以上的等待里,多少是任务真实执行时间,多少是排队和调度,多少是重复安装依赖、重复编译这种每次都做一遍的空转?三块混在一起,任何优化都像蒙眼投飞镖。
Linear 的做法是把 PR 等待时间和单个 runner 的执行时间分开看。这两个指标的量级完全不同:前者被队列和并发上限支配,后者才反映任务本身的开销。拆开之后方向立刻清晰——单测 runner 的时间是主要的可压缩项,而排队问题得靠并发策略解决,把测试写得更快并不能救它。
约束理论那套老办法,仍然好使
制造业五十年前就总结过:系统的产出由最慢的那一环决定,优化非瓶颈环节只会堆库存。CI 是典型的流水线。你花两周把 lint 从 20 秒优化到 8 秒,PR 总时间可能纹丝不动,因为真正卡住的是那个跑三分钟的单测任务,而且它正在跟十几个 PR 抢 runner。
所以 Linear 工程师的动作顺序是:先找瓶颈,再动瓶颈,动完重新找。这个循环听着朴素,但多数团队的实践恰恰相反——谁抱怨什么就先修什么,最后修的全是本来就够快的部分。
把 CI 拆成段,逐段挤水分
单测 runner:约减半是怎么来的
单元测试耗时减半,靠的不是某一个大招,而是一串互相叠加的小改动。测试套件本身要瘦身:真正需要重复验证的逻辑留下来,那些在集成层已经覆盖、在单测层再跑一遍只是浪费机器时间的用例,该删就删。执行方式也要改:能并行的一律并行,能按文件或模块切片的就切片,把一个大任务拆成若干个能在不同 runner 上同时开跑的小任务。
这里有个常见的坑:切片之后总觉得"再多切一点会更快"。切片过细,启动开销和调度开销会吃掉收益,失败定位也变难。合理做法是让切分粒度跟实际耗时分布匹配,而不是跟文件数量匹配。
缓存与增量:命中率比配置更重要
CI 里最容易被高估的东西就是缓存。配了缓存不等于用了缓存,用了缓存不等于命中。依赖装一次、编译产物复用,这些听着理所当然,实际命中率却常常低得让人难受——缓存键设计得太细,或者分支切换、锁文件微调就让缓存整体失效。
判断标准很直接:把命中率当成一个要盯的指标,而不是一个配完就不管的开关。命中率上不去,说明键的设计有问题;命中率上去了但时间没降,说明缓存的那部分本来就不是瓶颈。
分片并行,别只想着加机器
加机器最省事,也最容易失效。并发数一上去,排队可能缓解,但每个 runner 的启动、依赖拉取、环境预热成本也跟着翻倍,账单涨得比速度还快。Linear 的思路更接近"先把活儿分好,再看需要几台机器",而不是反过来。
还有一个容易被忽略的细节:并行任务之间的不公平。一个短任务和一个长任务同时进队列,谁先跑,直接决定 PR 的平均等待时间。长任务堵在前面,后面所有小改动都得陪跑。
为"机器写的 PR"重新设计流水线
提交节奏变了,队列策略也得变
人类提交代码有天然节流:要思考、要开会、要吃饭。Agent 没有这些。它可以整夜不停地产出分支和 PR,把 CI 队列变成一条永远排不满、也永远排不完的传送带。如果你的并发上限是按"团队人数乘以某个系数"估出来的,这个数字在 Agent 时代几乎肯定偏小——但盲目调大又会撞上更高的账单和更差的稳定性。
更实际的做法是把 PR 分层:真正需要全量验证的,和只需要跑受影响测试的。不是每个 Agent 生成的 PR 都值得消耗同样的机器资源,尤其是那些还在迭代、明显会被反复修改的分支。
快任务优先,别被慢任务堵死
调度上有个几乎零成本的优化:让快的先跑。单元测试、类型检查、lint 这些几十秒到一两分钟出结果的任务,先给反馈;端到端测试这种动辄十几分钟的,放到后面或者单独排队。整体完成时间可能没变,但开发者拿到"这版能不能过"的信号要早得多。
代价是要维护更复杂的队列规则,而规则本身也可能变成新的故障源。收益同样明确:反馈回路缩短,返工成本下降。
反馈前移:让问题在推之前暴露
AI Agent 写代码,最容易出问题的往往不是逻辑本身,而是它对你项目约定的陌生:导入路径、类型边界、测试写法、命名习惯。这些错误如果等到 CI 才发现,成本是一整个 PR 的等待时间。把它们挪到本地或提交前的钩子里——类型检查、受影响测试、格式校验、依赖方向校验——能挡掉相当一部分无谓的流水线往返。
5 分钟出头之后,还剩什么
数字之外,是心理上的松弛
从 6 分钟以上到 5 分钟出头,看数字像是挤牙膏。但对每天要合并几十个 PR 的团队来说,等待时间跨过某个阈值,工作方式会变。5 分钟以内,你愿意在等结果的时候顺手处理另一件事;超过 6 分钟,注意力就离开了,等你切回来时上下文已经冷透。真正省下的不是机器时间,是切换成本。
搬到你自己的 TypeScript 项目
这套方法没有多少 Linear 专属的成分。任何一个 TypeScript 单体仓库或 monorepo 都能照着做:先量 PR 等待和任务耗时,找出最慢的那一段;再对单测做减法、做切片、做缓存;调度上优先给短任务和高价值分支让路;把能在本地拦住的问题尽量前移。顺序错了收益就没了——先加并发、后删测试,通常只会得到一张更贵的账单。
没有终点这回事
瓶颈会搬家。你把单测压下去,端到端测试就会浮上来;你把流水线修顺了,代码评审和部署验证又会变成新的排队点。Linear 那篇复盘的价值不在某个具体配置,而在于它示范了一种习惯:把持续集成当成一个需要持续观察、持续调整的系统,而不是一条搭好就忘的管道。
Agent 还在进化,提交量的增长也远没到头。今天压到 5 分钟,明年可能又得重来一遍。这大概就是这个阶段工程团队的常态。

