Skip to content

Latest commit

 

History

History
214 lines (174 loc) · 13.7 KB

auto-run.md

File metadata and controls

214 lines (174 loc) · 13.7 KB

事件触发自动运行(GitHub events)

一览

状态 ⚠️ draft v3 —— 未实现。 本页描述的是一个提案,不是现有行为。仓库里没有任何触发层
想做什么 给 copilot 一个触发层,让它对 GitHub 事件无人值守地运行(先做 PR 自动评审,再做 issue 回答/分流)
今天的实际状态 每一次运行都是手动发起的。headless 构件已具备,触发器、幂等/归属规则、安全加固与放量门禁都还没有
落地前必须先有 安全加固(本文档列出的那几项)+ 允许发布的分阶段门禁;发布本身仍受 ALLOW_POST 约束
讨论 #3

读这一页时请记住:它是设计讨论,不是使用说明。别照着它配置任何东西。


原始 RFC

  • 状态:draft v3,供团队讨论(已折入两轮 gpt-5.6 评审)
  • 作者:tzhouam(+ copilot 分析)
  • 日期:2026-07-23
  • 讨论:#3

摘要

给 copilot 一个触发层,让它对我们的 GitHub 仓库无人值守地运行 —— 先做自动 PR 评审,之后再做 issue 回答/分流。今天每一次 run 都是手动发起的。 headless 的构件已经具备;本 RFC 覆盖触发器、幂等/触发归属规则、 必须先落地的安全加固,以及允许它发布的放量门。

动机

  • copilot 现在已被用在 vllm-omni 之外(AgentInfer、afd-plugin);issue #1/#2 就来自 这次采用,且都已修复。手动发起是仅剩的摩擦。
  • 实测包络(20-PR 战役,同一批 20 个 PR 上的 3 次生成 replicate —— 不是 60 个独立条目):eco 路径下每次评审约 $0.19、6–13 分钟; precision 0.777 ± 0.026、recall 0.430 ± 0.018,且与已录制基线之间存在有记录的 上下文不对称。读法:评论通常是对的,但覆盖是部分的 —— 它是一个额外的评审者,便宜到可以对每个 PR 都跑;绝不是人工评审的替代品。
  • 在 PR 打开后几分钟就到达的评审会改变作者行为;必须被请求才会发生的评审, 大多不会发生。

已经存在的东西

  • headless 一次性:-p "review <pr-url> and post the review" --yes; URL/裸引用的路由是确定性的(不花 LLM 意图调用),且显式的 post 意图能穿过它 (issue #1 的修复)。
  • 双闸的对外写入:命令必须携带 post 意图并且 ALLOW_POST=1; 达不到就是 dry run。推送安全(只有 force-with-lease、保护 main)不变, 且不在本 RFC 范围内。
  • pr.gate_check 报告 draft/合并态/失败 CI —— 作为评审上下文, 刻意非阻塞。触发层必须做自己的资格过滤(draft/closed/merged); 它不能倚靠这个 step。
  • 无人值守的失败路径:ESCALATION.md、退出码 3、升级邮件。

前置加固(任何自动运行之前的阻塞项)

自动运行把"敌意的 PR/issue 内容"从理论输入变成日常输入,而当前姿态存在真实缺口。 这些必须先落地:

  • H1 —— 读取受仓库边界约束。 今天 agent 的读取工具会把相对路径解析到该 run 的仓库根, 但绝对路径原样透传,而 PathScope 只约束写。一个被提示注入的评审 agent 可以读取任意宿主文件(密钥、.env)并把它们引用进输出。 修法:对 read_file/list_dir/grep/run_shell 的 cwd, 拒绝该 run 根/worktree 之外的绝对路径,并拒绝 .env*/密钥材料模式 (对话侧的 repo_read 已经有这套,把它应用到 step 运行时)。
  • H2 —— 出站清洗器。 在 pr.post_review 发布之前,扫描正文里凭据形状的字符串 (key 前缀、PEM 头、高熵 token),拒绝或脱敏并升级。这是"经评论外泄"的纵深防御; 注意 <untrusted_data> 围栏是 prompt 指导,不是执行边界 —— H1/H2 才是边界。
  • H3 —— 最小权限身份与隔离。 轮询器不得使用环境里的 gh 认证 (它可能持有很宽的权限)。要用专用机器账号 + 细粒度 PAT:仅限选定仓库、 PR 读写、别无其他 —— 且轮询器以一个专用 OS 用户身份运行, 它的环境里只有那个 PAT 和它自己的 LLM key,没有宿主的其他凭据。 这也让每一条被发布的评论都带上一个 bot 名字。
  • H4 —— 自动运行中不允许 agent 控制的持久化写入。 评审 agent 平时可以提出 skill candidate(skill_update_candidate);在自动运行下那是一条从注入通向持久状态的 路径,所以自动触发的 run 禁用知识写入工具。自动 run 是纯读 + 报告; 知识累积仍然是人工发起的流程。

设计选项

A. 在每个目标仓库里放一个 GitHub Actions workflow

pull_request(opened / ready_for_review)→ 安装 copilot → 一次性运行 → 用仓库 token 发布。

  • 优点:事件驱动,团队可见、可重跑,逐仓库选择加入。
  • 缺点:fork PR 拿不到 secret,且 pull_request 上是只读 token; pull_request_target 会把 secret 交给由不可信 PR 触发的 run —— 在 H1/H2 充分浸泡之前不可接受。临时 runner 会重置知识平面 (debug memory、skill 先验);只有已入库的 profile briefing 会留存。 LLM key 会扩散进每个仓库的 Actions secret。

B. 在 copilot 宿主上放一个轮询器(推荐的起点)

在那台已经跑着夜跑 rebase 的机器上,放一个由 systemd timer 驱动的 copilot-watch 脚本:逐个受管仓库,列出开放 PR → 资格过滤 → 去重(见下)→ 一次性运行 → 记录 + 发布(Phase 1+)。

  • 优点:继承整套现成设置(checkout、worktree 缓存)和持久的知识平面; 没有 LLM secret 被导出到 GitHub;约 100–200 行;与夜跑 rebase 同一套运维模式。
  • 缺点:触发对团队不可见(结果是可见的);单宿主;轮询延迟(见下)。

C. GitHub App / webhook 服务 —— 明确的非目标

那是产品化的形态;真实的托管 + 认证面。不适合我们这个规模。

提案

Phase 0 —— 只报告的影子运行(≥1 周)。 在团队提名的仓库上跑选项 B,H1–H4 已落地。 影子 run 完全不带 post 意图(一个真正只读的 TaskSpec —— ALLOW_POST=0 是保险带,不是机制)。评审累积在 run 目录里;升级邮件发给运维者。 由人类裁决输出(见下方的门)。

Phase 1 —— 发布。 对通过门的仓库逐个翻开 ALLOW_POST=1。 以 bot 身份经 gh pr comment 发布,带签名页脚 (copilot-review: <repo> #<pr> @ <head-sha> / <config-digest>)。 已知且刻意的限制:这是一条评论,不是 GitHub review 提交 —— 没有 APPROVE/REQUEST_CHANGES 状态,没有 inline thread。 升级到 gh pr review(以及 bot 是否可以设置阻塞状态)是另一份后续提案。

Phase 2 —— 可选的 Actions 前端(只是草图)。 面向想要团队可见触发器的仓库: 仅同仓库 PR(不用 pull_request_target),/copilot review 评论命令用于重跑, 且在使用任何 secret 之前先做发起者权限校验。 每个仓库只有一个触发归属:一个仓库要么由轮询器服务、要么由 Actions 服务, 绝不同时;迁移即转移归属。Phase 2 在这里刻意写得不完整 —— 它会有自己的 RFC(触发安全、权限检查、workflow 溯源)再谈实现; Phase 0/1 的任何东西都不依赖它。

触发与幂等规范(Phase 0/1)

  • 资格:开放、非 draft、未合并/未关闭、在该仓库启用时间戳之后打开 (不做历史积压扫荡)。
  • 去重键:(repo, pr_number) —— 一个 PR 只被自动评审一次,在它首次符合资格时。 新的 push(synchronize、force-push)不会重新触发;重评审只按需发生 (/copilot review,绑定新的 head SHA,且仅限有写权限的评论者)。 这让成本和评论噪声随 PR 数线性增长,而不是随 push 数。
  • 账本是缓存;GitHub 才是真相来源。 事务顺序:记 started → 运行 → 就在发布之前重新解析 head SHA 与 PR 状态(已推进/已关闭 ⇒ 标 stale,不发布)→ 发布 → 记 posted + 评论 URL。启动时以及任何崩溃不确定时, 通过查询 bot 的签名评论对账 —— 因此"发布与记录之间崩溃"不会重复发布, 而一条 started 却死掉的条目会重试而不是被抑制。 把被评审的 SHA 写进页脚,好让人类看得见 review-vs-head 的漂移。
  • run 状态与上限:账本条目按 started → reviewed → posted | stale | failed | post_unknown 流转。 failed 最多带退避重试两次,然后升级 —— 一个持续崩溃的 PR 不能每个周期都烧预算; post_unknown(已发布但未记录)只能由签名评论查询来解决。 每次 run 硬超时(30 分钟杀掉)、每日成本熔断(可配,例如跨仓库 $10/天)、 run 目录保留上限。
  • 排队要诚实:一次评审要 6–13 分钟;一个 worker、每周期 ≤4 次新评审、 跨仓库 FIFO。延迟是"轻载下约 15 分钟",在突发时线性增长 —— 队列年龄告警(不只是邮件)是健康叙事的一部分,与既有的 /status 和每周人工瞥一眼 账本并列。API/限流失败跳过该周期;连续 ≥3 次失败发邮件升级。
  • 防回环:触发器忽略由 bot 账号或任何 [bot] 发起者撰写的 PR 与评论 —— copilot 绝不因自己(或别的 bot)的输出而触发。

Phase-1 晋升门(逐仓库)

只报告的输出由仓库 owner + 另一位评审者裁决(分歧向更严格的标签收敛): 每条发现被标为 有用 / 中性 / 错误。在 ≥15 个 PR 且 ≥30 条发现上满足以下条件才晋升: 有用率 ≥ 0.75;≥50% 的 PR 至少收到一条有用发现(一个大多沉默的评审者不得通过); 零条严重错误发现(那种会误导合并的"错误阻塞项");且没有未解决的注入/外泄事故。 recall 被跟踪但不设门 —— copilot 是加法。签字:仓库 owner。 这是一道判断门,不是显著性检验 —— 那些下限的存在是为了让"有用率"有意义, 而 owner 可以在任何疑虑下让一个仓库继续留在影子期。

策略默认值(可辩论)

  • 评审深度 auto;eco 模型路径。
  • issue 自动回答:Phase 0/1 关闭;单独提案、单独的门 —— 错误的 PR 评论浪费评审者几分钟,错误的 issue 回答误导用户。
  • 具备写能力的 kind(pr_debug 的修复、rebase 推送):永久排除在自动运行之外。

安全考量

  • 发布仍然是双闸的;自动运行改变的是评审何时跑,不是它被允许做什么。
  • 注入的最坏情况(在 H1/H2 之后):一条错误或敌意的评论 —— 它本身可能触发别的仓库的评论 bot;bot 身份(H3)让其他自动化能过滤它, 而清洗器为外泄划定了边界。
  • fork PR:仅轮询器(H3 身份只持有评论权限); pull_request_target 保持禁用,除非被显式重新讨论。
  • 密钥:Phase 0/1 把所有 LLM key 留在 copilot 宿主上; Phase 2 才把它们加进参与仓库的 Actions secret。

回滚

  • systemctl stop timer 和 service unit(终止任何活动 worker/run), 然后吊销 bot PAT ⇒ 没有新 run,也没有在途的发布。
  • 已发布的评论不会被自动撤回:当原因是质量/事故时,回滚流程包括批量最小化 bot 的 评论(GraphQL minimizeComment),并在配置里关闭 /copilot review 处理。
  • Phase 2:删掉 workflow 文件;触发归属逐仓库回退。

给团队的未决问题

  1. Phase 0 覆盖哪些仓库?
  2. 晋升门(≥10 PR / ≥20 条发现上有用率 ≥0.75、零严重错误)是否合适, 除仓库 owner 之外还由谁裁决?
  3. "每 PR 一次 + 按命令重评审"是否可接受,还是有人希望每次 push 都重评审 (不顾成本/噪声)?
  4. /copilot review 是否应该落在 Phase 1(轮询器读评论)而不是等 Phase 2?
  5. 谁运维轮询器宿主、谁负责升级收件箱?
  6. 评论 vs GitHub review 提交:Phase 1 里"没有状态"这个限制是否可接受, 还是在任何人依赖它之前,inline/review 状态支持就是必需项?

评审记录

先由 copilot 的分析起草,然后两次经过我们的外部 plan-review 钩子(gpt-5.6)。 第 1 轮(裁决 REVISE)浮现、对照源码核实并折入了:绝对路径读取缺口(→ H1)、 gate_check 非阻塞的表述错误、评论 vs review 提交、synchronize/账本的矛盾 (→ 每 PR 一次的键控)、评测告诫的诚实性,以及事务性发布顺序。 第 2 轮(裁决 REVISE)补充了:skill candidate 写入作为一条注入路径(→ H4)、 不带 post 意图的影子 run、重试/超时/成本上限、防回环、门的下限, 以及"杀掉活动 worker"的回滚 —— 全部折入。 未采纳(记录以备讨论,判定在我们这个规模上不成比例): 带受限出网的完整容器隔离,以及把 Phase 1 阻塞在 GitHub-review 状态发布上。 评审者对这两点的立场可按需索取。