别让 AI 代理直接关掉 Bug:一套缺陷解决验证闸门
面向创始人的缺陷解决协议:让 AI 辅助分诊与修复,但不再把补丁生成、CI 通过或 PR 合并误当成用户问题已经解决。
一位客户反馈:账户币种一改,历史发票就消失。AI 编程代理在新建测试项目里复现出一条记录缺失,随后修改筛选逻辑、补上单元测试并提交 PR。CI 全绿,PR 合并,工单也被自动关闭。两天后,客户回复说问题还在——真实账户的历史数据里存在第二种币种代码,而复现样本从未包含这种情况。
这并不能证明 AI 代理毫无价值,只能说明团队把几个完全不同的状态都叫成了同一个词:已修复。
本文适合正在用 AI 代理分诊工单、生成补丁,或维护 AI 构建应用的创始人与小团队。核心判断是:只有当原始症状被固化成带环境信息的可复现样本、同一个候选制品通过独立检查,并由受影响用户或获授权的代验人在有代表性的环境里确认结果,Bug 才算真正解决。 看起来合理的诊断、新测试、绿色构建、预览、PR 合并乃至成功部署,都只能证明其中一个状态,不能替代后续证据。
你会得到一套解决状态机、证据矩阵、机器可读的解决回执、停止规则、指标和 48 小时试点方案。它适用于常见 Web、移动端、集成、开源库和内部工具,但不能替代正在被利用的安全漏洞、数据丢失、受监管记录、安全关键系统或无法安全复现故障的专业应急流程。这类情况必须先隔离风险,并交给明确负责的专家。
这也不是另一份 PR 审查清单。代码审查回答的是“这项改动能不能合并”;解决验证闸门回答的是另一个端到端问题:同一个、身份可核对的候选版本,是否从原始失败条件一路经过预览、发布和代表性确认,同时没有丢失证据,也没有悄悄更换谁有权接受结果。
先把“修好”定义成可验证的产品结果
缺陷报告是一个主张:用户观察到的行为与合理预期不一致。它可能确实是 Bug,也可能信息不完整、只在某个环境出现、其实是功能需求、文档缺口,或者双方对预期理解不同。分诊的任务,就是先区分这些可能性。 复现样本是一套可执行或可检查的条件,能够稳定触发报告中的症状。它不是把工单文字重新写一遍,而是要尽可能固定关键输入、账户状态、依赖版本、配置、平台以及预期结果和实际结果,使另一个人或系统也能看到相同失败。 候选修复是针对某个复现样本提出的改动。它可能只消除了这个例子,却没有消除根因;也可能修好一处、破坏另一处;还可能在预览环境有效,到了真实发布环境就失效。 已验证发布指的是:身份明确的候选制品、配置和数据迁移通过了必要检查,并在相关产品界面上产生了可接受结果。测试的是提交 A、发布的是提交 B,证据不会自动继承。 解决则是一个产品决策:原始义务已经被满足。它需要有责任归属的验证者。最理想的是报告者亲自测试预览或已发布版本;如果报告者无法参与,可由具名的产品、客服或领域负责人担任授权代验人,但必须使用保留关键条件的复现样本。“AI 说已经修好”和“工单太久没人回复”都不属于验证。GitHub 本身恰好能说明这一区别。其官方 PR 与 Issue 关联文档明确写道,在合并到默认分支时,fixes、closes、resolves 等关键词可以自动关闭关联 Issue。这是有用的工作流自动化,却不是语义证据:它不能证明报告者的环境已经正常、生产制品与测试制品一致,也不能证明下游数据已经恢复。创始人应把自动关闭当作一种可配置的状态迁移,而不是问题解决证明。
借鉴 Astro 的流程,而不是照搬那个百分比
2026 年 8 月,Cloudflare 与 Astro 维护者公开了一项真实生产实践。按照他们的 Astro 自动分诊流程复盘,彼此隔离的子代理分别负责复现、诊断、核验和提出修复;流水线把预览包发回原工单;报告者实际测试;确认有效后,自动化才创建关联 PR。Cloudflare 报告称,这项工作把未关闭 Issue 从 200 多个降到约 30 个。
这个降幅来自项目方自己的复盘,只代表一个开源项目,不能直接变成其他团队的效率基准。真正值得迁移的是流程结构:
- 复现、诊断、核验、修复被拆成不同阶段,没有全交给一次“把它解决掉”的连续执行。
- 阶段之间传递的是可查看的产物,而不是隐藏在对话记忆里的结论。
- 原始报告者测试由候选版本构建的预览包。
- 失败被明确建模;当前的 Astro Issue 队列能看到
needs reproduction、unable to reproduce、fix pending、fix rejected、fix verified、unable to fix等不同状态。 - 一个暂时无法解决的 Issue 可以保持可见,不必为了让自动化看起来成功而关掉。
需要补充信息、无法复现、符合预期、候选方案被拒绝、需要升级处理 成为合格输出。
不要把 85% 当作自己的目标。你面对的 Issue 类型、可测试性、报告者参与度、架构、风险和历史积压都不同。可以复制的是证据与决策权的分离,然后用自己的数据衡量结果。
用状态机代替一个“成功”复选框
把缺陷生命周期写成显式状态,每次只允许完成一项有依据的迁移:
| 状态 | 含义 | 必需证据 | 谁可以推进 |
|---|---|---|---|
reported | 收到症状与预期 | 原始报告、报告者、受影响界面、时间 | 接入系统 |
needs_context | 缺少会影响判断的条件 | 明确列出的待回答问题 | 分诊负责人或代理 |
reproduced | 在固定条件下出现同样症状 | 复现 ID、基线结果、环境指纹 | 独立执行器 |
not_reproduced | 已尝试但未出现症状 | 尝试记录与覆盖边界 | 分诊负责人 |
classified | 已判定为 Bug、预期行为、重复、客服、安全或功能需求 | 决策及证据链接 | 负责的维护者 |
candidate_ready | 已有补丁和候选制品 | 提交、Diff、测试、预览 ID、来源信息 | 构建系统与审查策略 |
candidate_rejected | 候选版本未满足结果 | 失败证据,返回诊断 | 报告者、授权代验人或发布闸门 |
candidate_verified | 候选版本通过复现与必要不变量 | 验证运行及验证者身份 | 独立验证者 |
released | 已验证制品进入目标环境 | 不可变发布 ID 与部署证据 | 部署系统 |
outcome_confirmed | 代表性使用中不再出现问题 | 报告者确认或代验确认、观察窗口 | 报告者或授权代验人 |
resolved | 产品责任人正式接受已完成 | 完整回执与负责人决策 | 产品负责人或既定策略 |
escalated | 常规自动化不安全或不足 | 严重度、负责人、隔离状态 | 任一控制路径 |
不是每个 Issue 都必须走遍所有状态。重复报告可以直接关联到主 Issue;文档误解可以转成文档修改并确认;安全报告则应该立刻离开公开自动化通道。状态机的价值来自每次迁移都有证据和负责人,而不是框越多越专业。
状态机必须放在模型自由裁量之外。开放的 Flue 代理文档展示了如何用 Issue 编号等稳定 ID 标识持久代理实例。持久化很有用,但它不是策略。允许的状态迁移、角色检查、到期时间和必填字段应由确定性的应用代码或工作流配置执行。模型可以提出迁移建议,控制层负责判断是否满足条件。
生成补丁之前,先冻结复现样本
最常见的假修复,从一个不断移动的目标开始。代理阅读报告、自己补全缺失事实、写出符合自身理解的测试,再让这个测试通过。补丁与“证明”共享了同一个误解。
因此,复现样本必须在候选修复出现之前,作为独立产物保存。至少记录:
- 报告者原话中的预期结果与实际结果;
- 保留关键条件的最小安全数据集;
- 相关的产品、浏览器、操作系统、运行时、依赖、语言地区、功能开关、权限和套餐版本;
- 准备步骤与操作步骤;
- 能触发失败的基线制品或提交;
- 已移除密钥的截图、日志、Trace ID 或响应体;
- 用于识别症状的断言;
- 暂时无法复现或检查的范围。
repro_id,后续所有测试和回执都引用它。如果诊断后必须修改复现样本,就生成新 ID 并说明原因。否则代理可能悄悄简化测试,直到自己偏好的方案通过。
结构化提问能改善输入,但不能保证报告正确。GitHub 的 Issue 模板文档支持表单和必填提示。可以询问预期行为、实际行为、最小步骤、版本、环境、样本或复现链接,以及维护者是否可以查看这些材料。不要要求用户公开客户数据、访问令牌或生产凭据;敏感问题应有私密升级渠道。
还要区分三种经常被混在一起的结果:
- 已复现:受控条件下确实出现了报告中的失败。
- 根因有支持:证据能把失败与诊断机制连接起来。
- 影响范围有边界:团队知道哪些相邻环境或数据形态已经测试、哪些尚未测试。
接受候选修复之前,先建立证据矩阵
解决闸门需要拼接来自不同失败面的证据。没有任何一个绿色检查可以代表全部。
| 证据类型 | 要回答的问题 | 最小产物 | 常见拦截对象 |
|---|---|---|---|
| 复现 | 基线是否出现原始症状 | 固定的失败运行 | 修复了想象中的问题 |
| 改动意图 | 哪些变化被授权 | 有边界的改动说明与非目标 | 顺手但未获授权的改造 |
| 候选身份 | 测试的到底是哪份代码与包 | 提交与制品摘要 | 测 A 发 B |
| 回归 | 固定症状是否消失 | 基线和候选对照测试 | 补丁没解决报告 |
| 不变量 | 相邻行为必须保持什么 | 既有、负向与边界测试 | 局部修复制造新故障 |
| 环境 | 代表性版本与开关是否正常 | 小型兼容矩阵 | 只在全新项目成功 |
| 用户界面 | 真实 UI、API、导出是否正确 | 预览或阶段环境观测 | 内部结果与产品界面不一致 |
| 安全与隐私 | 风险或数据处理是否改变 | 范围明确的审查与扫描 | 修复扩大权限或泄露诊断信息 |
| 发布 | 已验证制品是否真的部署 | 不可变发布与部署 ID | 生产与验证证据脱节 |
| 确认 | 报告者或授权代验人是否接受结果 | 身份、时间、观察、限制 | 把合并当成解决 |
NIST 的安全软件开发框架(SSDF)采用结果导向的方法,要求团队跟踪需求、风险、设计决策、来源、验证和漏洞响应。NIST 的开发者软件验证最低标准指南还强调了一个对小团队非常重要的事实:没有任何单一技术可以覆盖所有软件验证问题。验证深度应与后果匹配;修正一个错别字不需要和授权漏洞同样的矩阵,但 AI 生成的补丁不会因为 Diff 很小就自动成为低风险。
Google 发布的代码审查指南也要求审查者像用户一样思考,检查边界情况、并发问题,并确认测试本身有效。这形成了一条关键独立性规则:同一个代理可以同时提出代码和测试,但必须有另一个检查判断测试编码的是产品承诺,还是仅仅复制实现逻辑。
给报告者同一个候选版本,而不是一张截图
“预览里正常”只有在预览能够标识候选版本、且保留关键环境时才有意义。截图只能证明某个渲染状态曾出现,无法证明由哪次提交生成、是否用了报告者的输入、网络请求是否成功,更无法证明之后发布的是同一个构建。
如果维护的是软件库,可以发布按提交寻址的预览包。开源的 pkg.pr.new 项目记录了持续预览发布,以及把版本改写成 0.0.0-preview- 的方法,这能避免预览依赖与正式版本发生锁文件冲突。应用产品可使用绑定不可变提交和配置快照的隔离预览 URL 或测试租户;移动端可提供签名测试包;数据工作流则可使用脱敏数据集,并导出结果制品。
验证请求必须具体,例如:
我们已在 2.18.0 用复现样本repro_7f2a重现币种切换后发票消失。候选制品sha256:…91c只修改币种归一化。请在 2026-08-25 前使用附带的脱敏账户测试preview_4821,确认:(1)14 张历史发票全部显示;(2)总额没有变化;(3)切回日元后列表仍然保留。不要使用生产凭据。请回复“已验证”“未通过”或“无法测试”,并注明失败步骤。
这不是客服话术,而是一项使用者可理解的验收测试。拒绝应当便宜而安全。出现 fix rejected 时,系统应保存候选证据并返回诊断阶段,而不是让报告者与 AI 争论到底谁更正确。
如果报告者不再回复,也不能让 Issue 永久停在 fix pending。预先定义到期时间和代验规则。对于风险低、已充分复现的问题,客服或产品负责人可以依据固定样本和代表性环境代验,然后标记为 proxy_verified,同时保留这一限制。对于企业专属集成或高影响数据问题,没有客户确认时可以在监控下发布,但不能把它改写成“客户已确认解决”。
从测试到发布,始终保留制品身份
有效测试结果必须绑定到不可变制品,而不是 main 这样的分支名、可覆盖的容器标签或“最新预览”。根据风险记录源提交、依赖锁文件哈希、构建工作流版本、配置集、功能开关、迁移版本、制品摘要和部署 ID。
GitHub 的制品证明文档介绍了如何用摘要和工作流身份把构建来源绑定到二进制或容器。小应用未必需要企业级供应链体系,但至少需要它所体现的简单属性:测试对象和发布对象必须能够通过不可变标识进行比较。
执行三项检查:
- 候选检查:预览制品摘要确实来自已审查提交。
- 晋级检查:条件允许时直接晋级已验证制品,而不是重新构建一个未经说明的“同一份源码”。
- 运行检查:产品能够暴露或记录 Release ID,让客服把用户报告对应到实际运行版本。
风险较高的改动,应先把同一个候选制品发布到阶段环境或金丝雀流量。Google 的金丝雀发布指南把它定义为一次局部、限时的部署,并用选定指标与控制组比较。金丝雀可以发现复现样本遗漏的回归,却不能替代报告者确认——尤其当问题依赖某个客户特有流程、汇总指标根本看不到时。
让解决回执能够被机器校验
在 Issue 中保留人类可读摘要,同时生成一份自动化可验证的结构化回执。下面是一份紧凑起点:
resolution_receipt:
receipt_version: 1
issue_id: APP-184
classification: product_bug
severity: medium
expected_outcome: "切换币种后仍能看到历史发票"
reproduction:
id: repro_7f2a
baseline_release: web-2.18.0
environment: masked_customer_fixture_v3
baseline_result: fail
known_limits:
- "尚未测试 Safari 17"
candidate:
source_commit: 8f4c2d1
artifact_digest: "sha256:...91c"
preview_id: preview_4821
change_scope: currency_normalization
verification:
regression_suite: pass
invariant_suite: pass
security_review: not_required_low_risk_rule_v2
reporter_result: verified
reporter_evidence: issue_comment_9914
verified_at: "2026-08-22T08:40:00Z"
release:
deployment_id: deploy_7731
artifact_digest: "sha256:...91c"
canary_result: pass
outcome_confirmation:
result: confirmed
evidence: support_followup_551
observation_window: 24h
decision:
state: resolved
owner: product_oncall
decided_at: "2026-08-23T09:05:00Z"
以上值只是说明结构,不是 YBuild 客户数据,也不是推荐给所有产品的固定阈值。枚举值与必填字段应由代码定义。如果候选与发布摘要不同、验证发生在候选生成之前、验证者角色不满足要求或证据已经过期,系统必须拒绝状态迁移。
回执中不要保存密钥或原始客户数据。可以指向受访问控制和保留策略管理的证据。即使隐私政策不允许长期保存原始 Trace,也应保留解释决策所需的最小派生事实、哈希、批准记录与脱敏产物。
分离代理角色,也分离确定性控制
多个代理并不自动带来独立性。如果它们共享同一组未经检查的假设、可修改证据、权限和“必须成功”提示词,只是把同一个偏差重复了几次。真正的独立性来自不同的权限、输入和判定职责。
可以采用以下有边界的分工:
- 接入代理:整理报告并询问非敏感的缺失信息,无权关单或改代码。
- 复现代理:在隔离环境执行基线并冻结复现样本;基线断言完成前看不到候选补丁。
- 诊断代理:提出有证据支持的原因与不确定性。
- 补丁代理:只修改获授权范围,并生成候选测试。
- 验证执行器:针对候选摘要运行冻结复现、独立不变量与兼容矩阵。
- 报告者或授权代验人:在产品界面判断真正有后果的结果。
- 发布控制:用确定性规则检查回执字段、角色批准、制品身份和发布策略。
早期阶段还应远离高风险工具。接入或复现代理通常只需要读取 Issue、在沙箱执行,以及受控上传产物,不需要生产写权限、密钥导出、合并权限或部署权。Issue 正文与复现仓库都应视为不可信输入;沙箱的网络、密钥、资源和持久化边界,应与所执行代码的风险相匹配。
用失败案例测试闸门,而不是只跑顺利路径
启用自动推进前,准备一个小型对抗测试包:
- 报告其实是用 Bug 口吻描述的功能需求。
- 复现仓库里藏有要求代理泄露密钥的指令。
- 代理新写的测试在基线和候选版本上都能通过。
- 候选方案修好示例,却破坏了相邻语言地区或权限级别。
- 预览 URL 指向比回执更新的提交。
- 报告者用有效反例拒绝候选方案。
- 报告者尚未安装预览包,就回复“看起来不错”。
- 合并提交与已经验证的制品不同。
- 部署成功,但报告者账户的功能开关仍未开启。
- 可观测性改进被误当成问题解决。
- 报告者不活跃,系统便自动确认结果。
- 疑似安全漏洞进入公开复现通道。
还可以回放历史 Issue。按 UI、数据、集成、权限、性能和环境特定问题选一组有限样本,向自动化隐藏最终答案,再比较它的状态迁移与维护者当时真正使用的证据。这样能在现有客户成为测试对象之前,暴露 Issue 字段不足、样本夹具薄弱和默认规则不安全的问题。
衡量已验证解决,而不是美化工单队列
应跟踪一条漏斗,而不是一个速度指标:
- 收到的报告数;
- 信息足够的报告数;
- 尝试复现及成功复现数;
- 不同分类的数量;
- 生成的候选方案数;
- 被拒绝的候选方案数;
- 独立验证通过的候选数;
- 制品身份一致的已发布版本数;
- 报告者确认和代验确认的结果数;
- 在明确观察窗口内重新打开的 Issue;
- 可归因于已接受候选方案的回归;
- 各阶段人工耗时;
- 每个已确认结果的自动化成本。
还要按后果和可测试性分组。文档缺陷、行为确定的软件库 Bug 可能非常适合自动化;间歇性数据损坏、第三方集成、需要辅助技术验证的无障碍问题、账户特定授权失败,则可能需要更多人工或生产证据。诚实记录 not_reproduced 与 unable_to_fix,它们能暴露可观测性、架构、文档、样本夹具或产品预期的薄弱环节。
不要把 Cloudflare 的积压变化当成自己的预测。先用团队历史 Issue 建立基线。对于小团队,减少来回追问缺失信息的时间,或提高真正由报告者测试的候选比例,可能比短期增加关单量更有价值。
在 AI 找到补丁之前,就定义停止规则
出现下列情况时,阻断常规自动化并升级处理:
- 报告涉及正在利用的漏洞、凭据暴露、越权、数据丢失、支付错误、安全伤害或法定通知责任;
- 复现需要不受控的生产访问,或把敏感数据复制到未经批准的系统;
- 没有负责人能定义预期行为,或文档、测试与客户承诺彼此冲突;
- 代理无法构造会在基线上失败的断言;
- 候选改动超出获授权范围;
- 测试太不稳定,无法区分基线与候选;
- 报告者给出有效的拒绝案例;
- 无法核对候选、预览与发布身份;
- 对高后果变更,唯一验证者仍是同时编写补丁与测试的代理;
- 高影响发布没有可用回滚方案。
用 48 小时做一次有边界的试点
0–6 小时:选一条通道。 只选择改动可逆、能够安全复现的低至中风险 Bug。排除安全、支付、权限、破坏性迁移、受监管数据和正在发生的事故。写清谁可以分类、验证、发布和正式解决。 6–14 小时:定义状态与证据。 配置生命周期字段、允许迁移、到期时间、代验规则和停止规则,建立解决回执 Schema。在试点通道里禁用过早关单的合并关键词;PR 可以关联 Issue,但不提前承诺已经解决。 14–24 小时:准备夹具与隔离。 建立一套脱敏复现环境,固定依赖和开关,限制代理凭据与网络,确保基线和候选运行分别产生可保留、可区分的结果。 24–34 小时:回放历史问题。 选择 3–5 个已解决案例,并加入至少一个非 Bug、一个被拒绝候选和一个环境特定故障。检查自动化是否问对问题、是否停在正确状态。 34–42 小时:影子模式处理一个真实候选。 让代理提出状态迁移和产物,但由人执行。把同一个候选预览与有边界的验证请求发给报告者或授权代验人。 42–48 小时:审查回执。 核对制品身份、证据链接、角色分离、隐私处理、拒绝路径和指标。只启用已经产生可靠证据的迁移。在多次影子运行证明更窄的自动化安全之前,合并、部署与最终解决仍由人控制。试点成功的标准,是团队能解释每一次迁移为什么发生,也能从错误候选中恢复;它完全不需要自动关闭任何 Issue。
知道何时这套闸门过重,又何时仍然不够
如果只是没有用户、没有持久义务、没有集成、无需生产连续性的可丢弃原型,一份短复现加冒烟测试可能就够了。不要围绕探索代码建立官僚流程,但要明确记录它可以丢弃,并让它远离有后果的数据和权限。
对于成熟、测试充分、行为确定的软件库,Issue 模板、回归套件、预览包、审查规则、发布来源和维护者实践可能已经覆盖闸门的大部分内容。应让解决回执连接这些既有系统,而不是重复建设。
安全响应、隐私事故、财务核对、医疗或安全关键系统、受监管记录以及不可逆迁移,仍远超这套闸门能力。它们需要专业流程、风险隔离、必要时的法律或合规判断、更深入验证和更长监控。报告者确认很有价值,但不能豁免安全、正确性或监管义务。
最终原则其实很克制:让 AI 加速证据生产,但不要让 AI 重新定义什么叫证据。 生成的补丁可以很优秀,绿色测试可以很有意义,合并后的 PR 可以已经具备发布条件,经过验证的预览也可以带来信心。只要把这些主张分开,“已解决”就会成为可辩护的产品结果,而不是一个被工作流刻意优化出来的标签。
缺陷解决闸门检查清单
- [ ] 用用户语言写清预期结果与实际结果。
- [ ] 在生成补丁之前冻结基线复现样本。
- [ ] 记录环境、输入、版本与已知限制。
- [ ] 完成 Issue 分类;安全和敏感数据问题能够离开常规通道。
- [ ] 改动范围与非目标明确。
- [ ] 候选提交、预览和制品都有不可变身份。
- [ ] 同一复现样本在基线上失败、在候选版本通过。
- [ ] 独立测试不变量与代表性环境。
- [ ] 报告者收到同一个候选版本和具体验证步骤。
- [ ] 拒绝、沉默与代验是不同状态。
- [ ] 已验证制品与已发布制品完成核对。
- [ ] 记录结果确认、观察窗口和负责人。
- [ ] 回执字段与角色检查由模型之外的系统执行。
- [ ] 停止规则、回滚和升级路径已经测试。
- [ ] 指标明确区分候选输出、已验证发布与已确认解决。
参考资料
- Cloudflare:How we built a software factory to drive Astro’s GitHub issue count to zero
- Astro:GitHub Issue 队列与分诊状态
- Flue:Building agents
- GitHub Docs:Linking a pull request to an issue
- GitHub Docs:Configuring issue templates
- StackBlitz Labs:pkg.pr.new continuous preview releases
- GitHub Docs:Using artifact attestations to establish build provenance
- NIST:Secure Software Development Framework
- NIST:Guidelines on Minimum Standards for Developer Verification of Software
- Google:What to look for in a code review
- Google SRE:Canarying Releases
- Anthropic:The AI-Native SDLC playbook