https://github.com/BililiveRecorder/BililiveRecorder/pull/801
最终改动很小:两个文件,18 行新增。其中真正的行为修复只有 4 行,另外 14 行是回归测试。
但走到这 18 行之前,我和 AI 绕了很大一圈,也给仓库维护者制造了不少额外工作。这篇不是模型横评,也不是成功案例包装,而是一次关于 AI 编程、开源协作和责任边界的复盘。
文中提到 5.6 Luna 和 Sol ,只是为了还原当时的任务分工。真正值得讨论的不是哪个模型更强,而是:当提交者把工作交给 AI 后,应该怎样避免把 AI 尚未完成的推理成本转嫁给维护者。
起因:我想帮忙,但不想深入学习整个仓库
我在使用 BililiveRecorder 时遇到了一个偶发问题:直播已经结束,但录制任务没有正常退出。界面显示仍在录制,速率是 0.00 Mbps ,也没有创建新文件,房间一直被异常任务占用。
这是一个真实存在、也影响继续录制的问题。我既然碰到了,就想顺手帮开源项目把它修掉。
但这个仓库不是我熟悉的项目。我不太想投入很多时间重新学习它的录制状态机、网络层、FLV 解析器和测试结构,于是选择把排查、改代码、跑测试、提 PR 、回复 Review 几乎全部交给 AI 。
有点像三国杀里的刘禅:我负责说“放权”,然后期待 AI 自己把事情办完。
这本身未必错。AI 的价值之一,就是降低进入陌生仓库的成本。问题在于,我不仅把执行交了出去,也把判断和验收一起放弃了。
第一次错误:把“听起来合理”当成“已经找到原因”
最开始使用的是偏快的 5.6 Luna 。它根据日志和表面状态,很快给出了几种听起来都很合理的解释:
这些都不是完全胡说。它们在代码层面确实是值得检查的边界,有些修改甚至能通过测试和 CI 。
但它们没有回答最关键的问题:异常任务当时到底卡在哪里?
我们只有“直播结束后任务没退出”这个现象,却已经开始改 Room 生命周期、取消令牌传播、重试时序和 watchdog 。每出现一个新疑问,AI 就再补一个机制。改动越来越像重构,根因却仍然只是猜测。
第二次错误:把 PR 当成了 AI 的公开调试现场
更糟的是,我没有在本地把这些猜测拦下来,而是让 AI 直接把它们放进 PR 。
维护者随后提出了几个很关键的问题:
这些本来应该是我在提交 PR 之前完成的内部 Review 。
但当时我的处理方式仍然是:把维护者的问题继续交给 AI ,让 AI 写解释、换假设、改代码、补评论。PR 逐渐变成了 AI 推理过程的流水账。维护者不得不代替我检查证据是否成立、改动是否真的命中问题、测试是否有意义。
这实际上是把我的节省,变成了维护者的额外成本。
维护者有一句反馈很直接:这个过程有点像在当 AI 的“肉身代理”。模型没有把事情想清楚,我却负责把它的输出搬进真实协作场景。
这句话并不好听,但很准确。
一个特别典型的失误:诊断版本没有被证明真的运行过
中间我们编译过一个增加大量 verbose 日志的版本,理论上会记录网络读取、Pipe 状态、FLV 解析、文件创建和 watchdog 数据。
后来又出现了真实故障,我上传了日志,并基于它推导出新的修复方向。
维护者指出:这份日志里根本没有诊断提交新增的字段,看起来和发布版日志没有区别。
这意味着我们连“当时运行的是诊断版本并且日志开关已经生效”都无法证明。既然诊断工具没有被验证,它的缺失字段也就不能支持任何新结论。
正确流程应该是:
[ol]
[/ol]
我们跳过了中间的验证,却开始解释结果。这是典型的“有了工具的形式,却没有获得可信证据”。
转折:异常进程还活着,先别重启,抓 dump
真正的转折发生在故障现场还没有消失的时候。
当时异常录制任务仍然存在。我本来需要清理它,否则后续直播无法正常录制。在重启之前,我们先对进程抓了一份约 1.36 GB 的 full-memory dump 。
dump 通过 Windows dbghelp.dll 的 MiniDumpWriteDump 获取,分析使用 .NET 的 ClrMD 。没有公开上传 dump ,因为完整进程内存可能包含 Cookie 、URL 和其他敏感数据。
这次换成 Sol 继续分析。它从托管堆里找到对应房间和录制任务,再沿异步状态机查看录制循环、TagGroupReader 、FlvTagPipeReader 和底层 Pipe 的状态。
dump 显示:
结合源码和 .NET Pipelines 的语义,根因终于清楚了:
当输入不足 9 字节、无法解析 FLV header 时,旧代码直接执行 continue,没有先检查这次读取结果的 IsCompleted。
而一个 writer 已完成、buffer 为空的 Pipe ,再次调用 ReadAsync 会同步返回同一个 completed 结果。于是代码不断经历:
读取 completed + empty
解析 header 失败
continue
再次同步读取 completed + empty
它形成了一个无限同步循环,既不退出,也不给正常清理逻辑机会。
修复只需要在继续循环前检查 result.IsCompleted 并退出。随后增加一个测试:完成一个空 Pipe ,调用解析器,应该立即返回 null,不能挂住。
此前所有 Room 轮询、延迟重试、watchdog 、网络取消相关改动都被回退。最终 PR 相对比较基线只剩两个文件和 18 行新增,并在 CI 全绿后合并。
Sol 找到了答案,但“换强模型”不是完整结论
这次体验确实让我感受到:面对陌生大型仓库、异步状态机、运行时对象和 dump ,强推理模型更适合承担最后的因果分析和收敛工作。5.6 Luna 在这次任务里更容易沿着表面线索快速产出“看起来能修”的改动,而 Sol 更能坚持把对象状态、运行时语义、源码和测试串成完整证据链。
但如果把结论简单写成“Luna 不行,换 Sol 就好了”,仍然是在逃避责任。
Sol 能收敛问题,一方面是模型能力,另一方面是任务条件终于变了:我们有了真实现场 dump ,并明确要求先证明根因、回退未经验证的改动、让 PR 尽可能小。
如果没有 dump ,没有证据门槛,只是继续要求更强模型“赶紧修掉”,它同样可能给出一个更有说服力、但仍然未经验证的故事。
真正需要负责的是提交者
我是 PR 的提交者。无论代码由我写、AI 写,还是多种模型接力写,维护者看到的提交都代表我。
我可以不熟悉整个仓库,也可以依赖 AI 降低学习成本,但至少要承担以下责任:
我当时没有做到。因为想少花精力,我把判断完全交给 AI ;因为不想自己 Review ,我实际上让维护者替我 Review AI 的中间产物。
这不是合理使用开源维护者的时间。
下次我会采用的流程
以后再遇到类似问题,我会把流程固定为:
准确描述现象
↓
盘点已有日志、dump 、trace 和版本信息
↓
证据不足:只准备诊断工具,不先修改行为
↓
验证诊断构建和日志开关确实生效
↓
等待复现,并在清理现场前保存日志、dump 或原始输入
↓
从证据确认原因,而不是从症状猜修复
↓
写一个能在旧代码失败的最小测试
↓
做最小修复,回退探索性改动
↓
内部 Review 、测试、CI 、检查完整 diff
↓
最后才提交或更新 PR
模型也会按任务分工:
还有一个简单的提交门槛:如果我不能用几句话说明“证据是什么、原因是什么、这几行代码为什么能解决它、测试如何证明”,就还不应该把修复交给维护者。
结语
我仍然认为 AI 会显著降低普通用户参与开源项目的门槛。过去需要熟悉几周才能进入的仓库,现在可能几小时就能完成定位和修复。
但 AI 加速的不只是正确过程,也会加速猜测、代码膨胀和没有证据的自信。如果提交者完全退出判断环节,节省下来的时间很可能只是转移到了维护者身上。
这次 PR 最后得到了正确且很小的修复,也顺利合并。但前面的弯路本来可以避免。
下次我仍然会让 AI 干大部分活,只是不再当完全放权的刘禅。AI 可以代写代码、分析日志甚至检查 dump ,但提交者必须对证据、范围和最终交付负责。

