junnhwan
全部文章

Weekly6:PR-Agent 的状态上报与边界修复

记录这周合并的若干开源 PR:PR-Agent 的失败上报、分片 fallback 补跑、webhook 投递去重与 GitLab 提取优化,以及 k8sgpt 三个 analyzer 的边界修复与新增。

写在前面

这周开源这边新做的 PR 不多,更多是之前提的几个陆续被合并,再加上周一新开的两个。项目上主要是 PR-Agent 和 k8sgpt 两条线。

把这几个改动放在一起看,发现它们指向的问题挺接近的:一个流程”跑完了”并不等于”成功了”,而很多 bug 都出在被默认假设忽略掉的边界条件上。下面就分别讲一下。

PR-Agent:失败状态的如实上报

PR #3171 修的是命令失败之后,GitHub 上的 outcome reaction 仍然显示成功的问题。

GitHub 评论处理器会用 PRAgent.handle_request() 返回的布尔值来决定打哪个 reaction。但 review、describe、improve 这些工具在 propagate_tool_errors = false(这是出厂默认的兼容行为)时,会把内部的 provider/模型错误捕获掉然后正常返回。于是处理器拿到的是 True,即使命令其实失败了,也会加上 reaction_on_success。

原来的 reaction 测试只用了直接返回 True/False 的假 agent,没有覆盖真实工具”吞掉错误还是抛出错误”这套契约,所以这个问题一直没被测出来。

这次给 handle_request() 加了一个 request-scoped 的 propagate_tool_errors override,沿着 Agent 路由传下去,在仓库和命令配置处理完之后再生效,并在 finally 里恢复原值。只对那些结果会被 react_to_outcome() 消费的 GitHub 评论命令打开这个 override。不传 override 的调用方行为完全不变。

这里的边界是:它只让 reaction 如实反映结果,并没有去改动工具层”默认吞错误”这个全局行为——那是兼容默认值,动它影响面太大。

PR-Agent:分片失败后用 fallback 模型补跑

PR #3324 处理的是 /improve 在大 Diff 分片场景下,部分 chunk 失败时的结果缺失。

/improve 会把大 Diff 拆成多个 chunk 分别生成建议。假设 chunk A 和 C 成功、B 抛了异常,现有的 partial-success 策略会认为”已经有部分成功了”,从而阻止外层 fallback 链再去重试 B。最终用户拿到的是 A/C,B 的建议就这么静默地丢了。

这次加了一个 opt-in 的 pr_code_suggestions.recover_failed_chunks,用剩下配置好的模型只补跑失败的那些 slot,不重新生成已经成功的预测。补跑时保留原始 chunk 顺序,每个 batch 跑完再切换 deployment,完成或取消时恢复 deployment 状态。切换前还会拿完整渲染后的 prompt 去比对每个 fallback 模型本地估算的 token 预算,不兼容的模型直接跳过,而不是截断上下文。默认关闭。

简化后的流程大致是:

收集成功 chunk 的预测 + 失败 chunk 的 slot
→ 对失败 slot 依次尝试剩余模型(先过 token 预算)
→ 按原始顺序合并 A/B/C
→ finally 恢复 deployment 状态

需要说清楚的边界:开启后会增加推理成本、可能增加延迟;补跑只限于单次调用、只处理抛异常产生的失败,没有持久化的断点续跑,也不会重新分片;YAML 解析失败仍走原来的 parse-failure 计数。

PR-Agent:webhook 投递去重

PR #3374 修的是同一个 webhook delivery 可能被重复执行的问题。

GitHub webhook 的后台任务原本会丢掉 X-GitHub-Delivery 这个投递标识,于是同一次投递有可能把同一条命令再跑一遍。

这次保留了这个投递身份,并在每个 GitHub App worker 内做了 opt-in 的重复抑制。开关 github_app.webhook_delivery_deduplication 默认 false,不打开时手动重投的行为完全不变。打开后复用 push_trigger_slot 和 DefaultDictWithTimeout,以 delivery ID 为 key:正在执行的任务在整个执行期间都受保护,成功的投递按部署已有的 push_trigger_pending_tasks_ttl(默认 300 秒)保留;失败、异常和取消则让该投递可以重试。重复请求不会延长这个完成窗口。

这个去重是进程内内存状态,重启即清空,worker 和副本之间互相独立——也就是说它解决的是单 worker 内的重复投递,不是一个分布式锁。这一点在 PR 里也写明了。

PR-Agent:GitLab ticket 提取少一次请求

PR #3322 是一个性能优化。

GitLab 的 ticket 提取在读每个 issue 之前,会先去取一遍该 issue 所属 project 的 metadata,但实际上后面只用到了 issue manager。等于每个 ticket 都多花了一次带鉴权的 project 请求。

这次改成用一个 lazy 的 python-gitlab project handle,让每个 ticket 只需要一次 issue 请求。ticket 数量上限、排序、正文截断、单 issue 的错误处理、模型 fallback 和发布路径都保持原样。

PR 里的离线 benchmark(真实 python-gitlab + 内存 HTTP adapter,三个关联 issue)显示 HTTP 请求从 6 次降到 3 次;在每请求模拟 5ms 延迟时,提取 p50 从约 37ms 降到约 19ms。需要强调的是这测的是离线的上下文提取,不是生产或端到端 review 延迟。

k8sgpt:三个 analyzer 的边界修复与新增

除了 PR-Agent,这周 k8sgpt 也有三个 PR 被合并,都和 analyzer 的判断条件有关。

PR #1750 修的是 ReplicaSet 的漏报。原来的 ReplicaSet analyzer 只在 if rs.Status.Replicas == 0 这个分支里检查 status.conditions。于是一个已经起了部分 Pod、但扩容失败的 ReplicaSet(status.replicas > 0 且 ReplicaFailure=True/FailedCreate)就被这个副本数判断挡在外面,明明创建 Pod 失败了却什么都不报。ReplicaFailure 本来就和已有多少 Pod 无关,这次把它独立于副本数来判断,范围先收敛在 FailedCreate 这个 reason。

PR #1755 修的是 RoleBinding 引用解析。Security analyzer 之前总是走 namespaced 的 Roles API 去解析 RoleBinding 的引用,忽略了 roleRef.kind。结果是引用 ClusterRole 时,如果没有同名的 namespaced Role 就被静默跳过,或者更糟——分析了一个不相关的同名 Role。这次按 roleRef.kind 决定是查 namespaced Role 还是 cluster-scoped 的 ClusterRole。ClusterRoleBinding 等其他 analyzer 不在这次范围内。

PR #1765 是新增了一个可选的 ValidatingAdmissionPolicy analyzer,针对 Kubernetes v1 的 admission policy 资源:上报 status.typeChecking.expressionWarnings 里的 CEL 类型检查告警、上报 spec.policyName 悬空的 Binding,并且独立于 label 选择去解析 binding 目标,避免被引用的 policy 不匹配 analyzer selector 时产生误报。它注册为可选 analyzer,默认的核心 analyzer 行为不变。

这三个放一起看,前两个都是同一类问题:analyzer 里一个想当然的判断条件(副本数为零才检查、引用一律当 namespaced Role),在边界情况下直接导致漏报或误报。

写在最后

这周的 PR 跨了两个项目,但落到问题上挺一致。

PR-Agent 这几个都在处理”失败”和”重复”的可见性:reaction 要如实反映命令是否真的成功,分片部分失败时不能让成功的结果掩盖掉失败的 slot,同一次投递不应该被跑两遍。它们共同的前提是——一个流程正常返回,不代表它真的成功;一旦被拆成多个部分(chunk、delivery),失败和去重就得单独处理。

k8sgpt 那两个修复则是另一种边界:analyzer 里的默认假设(副本数为零、引用是 namespaced)在真实集群的边界条件下不成立,于是该报的没报、不该报的报了。

性能那个 #3322 算是这周唯一不涉及正确性、纯粹减少重复工作的改动,benchmark 也只是离线数据,不代表生产环境的提升。