
Autofix Round Heartbeat为长耗时 PR 修复轮次注入存活与进度信号【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文基于 qwen-code 仓库的设计文档 docs/design/autofix-round-heartbeat.md讲解 autofix 自动化管线中一个真实痛点的解决方案review-address轮次可能运行数小时期间 PR 上只有一个静态的 AutoFix is working状态评论健康的慢轮次与卡死的轮次在 PR 页面完全无法区分。围绕该设计文档本文完整还原心跳heartbeat机制的背景、方案、实现与安全考量并对照仓库中已落地的源码.github/scripts/autofix-status-heartbeat.sh、.github/workflows/qwen-autofix.yml说明其真实工作方式。读完你将掌握如何在 GitHub Actions 长任务中让状态评论活起来、如何安全地在一个持 PAT 的上下文中运行周期性后台进程、以及如何用生命周期校验、身份自检与年龄上限把这套机制收敛在可信边界内。一、问题背景长轮次中的信号黑洞1.1 一个轮次能跑多久在 qwen-code 的 autofix 流水线中一个评审-修复review-address轮次的时间预算相当大Triage and address步骤单独就有130 分钟超时其中 agent 预算为 120 分钟进入修复repair路径的轮次可以占据整个 job 的330 分钟预算。期间 PR 上只有一个静态信号——由Post autofix status comment步骤发布的!-- autofix-status --评论内容固定为 AutoFix is working on this PR — round N/M此后直到最后一刻Finalize autofix status comment运行之前这个评论不再有任何变化。1.2 观测到的失败案例设计文档记录了 PR #9739 上的真实观察2026-08-22/23round 2 在 00:30 UTC 被派发运行了约 1.5 小时才发布任何东西。对盯在 PR 页面上的维护者来说一个健康的长时间轮次和一个已死的轮次完全无法区分唯一的手段是打开 Actions 运行记录、翻日志。问题的本质是在轮次开始与轮次结束之间没有任何存活liveness或进度progress信号到达 PR 本身。二、现状盘点相关步骤的职责与边界要设计心跳必须先弄清楚流水线里哪些步骤在什么上下文中运行、持有什么凭据。设计文档逐一列出了相关步骤这些步骤在 .github/workflows/qwen-autofix.yml 中均有对应实现步骤位置qwen-autofix.yml职责是否持有 PATPost autofix status comment~L5173upsert 状态评论每个 PR 一条每轮 PATCH并把comment_id通过 step outputs 交给 finalize是GITHUB_TOKEN: secrets.CI_DEV_BOT_PATTriage and address~L5336运行run-agent.mjs把每个模型/工具事件流式写入${WORKDIR}/agent.log否刻意不持 PAT——agent 执行的是 PR 分支内容绝不能看到 bot 凭据Verification gate~L5446第一个在宿主机上运行分支代码构建/测试的步骤否Finalize autofix status comment~L6842if: always()减去 stale/dry-run把同一评论 PATCH 为终态文本是Clean up autofix workdir~L7005最后一个步骤if: always()删除${WORKDIR}—Reset autofix workspace~L3874下一轮同 PR 运行开始时再次删除${WORKDIR}并对/tmp/autofix*做年龄清扫—几个关键事实Watch live progress 链接指向运行页actions/runs/run_id离正在进行的 job 日志还差一次点击。信任暂存trust staging纪律Prepare branch and feedback把工作树切到 PR 分支之后所有要调用的脚本必须来自RUNNER_TEMP中从可信基线检出trusted base checkout暂存的副本。工作树中的那份此时是 fork 代码。仓库里已有两条成熟做法暂存副本 调用时重新校验 sha256 摘要以及把脚本内容放进GITHUB_OUTPUTheredoc供在 agent 之后运行的持 PAT 步骤使用。这些约束直接塑造了心跳的设计——它必须活在review-addressjob 内部、必须持有 PAT、因此必须被严格的生命周期纪律包围。三、方案 A让状态评论活起来的心跳循环3.1 核心思路在Post autofix status comment结束时PAT 已在环境中、评论 id 已知启动一个脱离步骤的detached后台循环每约 10 分钟 PATCH 一次同一个状态评论在原有的双语working文本之外追加一行进度信息⏱ Running for 42 min · agent active 3 min ago (⏱ 已运行 42 分钟 · agent 最近活动在 3 分钟前)两个关键指标Elapsed已运行时长从轮次开始起算的墙钟时间。Agent activityagent 活动${WORKDIR}/agent.log的mtime——不做任何解析、不依赖事件结构。文件尚不存在时显示 agent starting。思考阶段可以合理地静默到run-agent.mjs的 10 分钟流空闲窗口所以这里显示的是诚实的事实而非经过解读的结论。3.2 让循环脱离步骤边界循环必须活得比启动它的步骤更久实现上三管齐下stdout/stderr 重定向到${WORKDIR}/heartbeat.logstdin 来自/dev/null通过setsid启动让它在常驻 runner 池上拥有自己的进程组。为什么必须认真对待孤儿进程这个管线跑在常驻自托管池persistent self-hosted pool上孤儿循环是不可接受的——它会在每轮结束后的任意时刻继续持着 PAT 向 PR 发 PATCH。3.3 生命周期与终止纪律六条规则设计文档给出了完整的 kill 纪律逐条对应源码实现心跳被限定在沙箱化的 agent 阶段内。Verification gate是第一个在宿主机上agent 阶段在 docker 沙箱内运行分支代码的步骤所以它在启动 gate 之前先杀掉循环。心跳覆盖最长阶段≤130 分钟的 agent 步骤评论在 gate/repair 期间保持最后一次心跳的文本最后由 finalize 翻转成终态文本。Kill 目标通过表达式上下文传递。启动时把$!记录为heartbeat_pidstep outputgate / finalize / cleanup 都杀这个值——经由各 kill 步骤的 step 级env:块即STATUS_ID形态传递让 runner 把它当作数据设置如果在 run body 里做插值伪造的 output 会在 shell 解析之前被替换从而作为 shell 语法在消费方 shell 中执行R16-1。杀的范围覆盖pid、它的进程组、以及它的整个会话每个 tick 的timeout 60 gh子树coreutilstimeout默认行为运行在循环 setsid 会话之下的独立进程组中只杀 pid/进程组会让它活着继续持 PAT 长达 60 秒。从 WORKDIR 文件读 pid 是不可信 kill 目标——agent 的 docker 沙箱把宿主机/tmp挂在同路径、以同一用户运行agent 执行的分支代码可以往那里写入任意值。磁盘上的${WORKDIR}/heartbeat.pid仅供诊断与循环自身的自检。每次 kill 额外做生命周期确认lifecycle-confirmed启动时记录循环的开始时间heartbeat_start_ticks即/proc/pid/stat中的 start time 字段killer 只对一个 stat 中仍带着完全相同开始时间的 pid 发信号——启动与 kill 之间被复用的 pid 携带不同的开始时间死掉的 pid 没有 stat因此检查失败就意味着什么都不该杀。Finalize autofix status comment触碰heartbeat-stop、执行生命周期确认的kill并在自己的 PATCH 之前排空drain在途的心跳戳每个 tick 在 gh 调用有 60s 上限前后分别写入/删除heartbeat-tick-inflight内容为 tick 的开始 epochfinalize 一直等到该戳消失或超过 65s 完成界。为什么不能只 kill杀死客户端无法取消服务器已接受的 PATCH——这里替换掉的那个固定 2 秒 sleep 是被探针证伪的一个迟到的 WORKING 提交发生在终态文本之后把评论又翻回了看起来还活着。Clean up autofix workdiralways()再次 kill作为兜底belt-and-braces。Reset autofix workspace不 kill跨运行的 pid 必然来自不可信文件类。清空WORKDIR会移除 pid 文件循环在下一轮复用该宿主机时会在下一次身份自检中自行退出跨宿主机时无人重写 pid 文件崩溃遗留的孤儿会持续脉冲它的陈旧 body直到年龄上限——与活轮的 body 交替出现并在 finalize 后一个间隔内覆盖后续轮次的终态文本。最坏同主机情形一个已经过了身份检查的陈旧 working tick 被新一轮重新 PATCH。跨主机窗口是被接受的残余风险由年龄上限兜底。循环内部的自退出边界身份检查identity check而非存在性检查WORKDIR是 PR 作用域的/tmp/autofix-review-pr下一轮会在同一路径重建heartbeat.pid存在性检查会让孤儿通过。循环只读文件做自识别、从不 kill 任何东西因此这里的读是安全的且读取有时间timeout 5与字节head -c 64双重限制防止 WORKDIR 中的 FIFO 或指向/dev/urandom的符号链接阻塞/撑爆这个持 PAT 的进程R17-1若heartbeat-stop存在则退出到达硬年龄上限设定在 330 分钟 job 信封稍后则退出——活轮的循环会在 gate 或 finalize 处早于 job 结束死亡只有崩溃孤儿能到达上限而上限约束了该孤儿在/proc/pid/environ中持有 PAT 的时长每个 tick 的gh调用包裹在timeout 60内黑洞连接无法把循环拖过上限。3.4 Kill 逻辑内联而非脚本调用设计文档明确要求 kill 逻辑内联在 yml 中每个 4~6 行而不是调用脚本killer 运行在持 PAT 或 agent 之后的步骤中此时从 PR 分支工作树执行脚本正是暂存纪律要阻止的换包swap风险。源码中的实际形态见Verification gate、Finalize autofix status comment、Clean up autofix workdir三个步骤与此一致——全部使用绝对路径命令字/usr/bin/touch、/usr/bin/cat、/usr/bin/kill、/usr/bin/pkill因为外层 shell 会继承$GITHUB_ENV中的植入。四、方案 B把 Watch live progress 锚点深链到 jobPost autofix status comment现在会解析正在运行的review-address矩阵腿matrix leg的数字 job id列出当前 run attempt 的 jobs按名称前缀review-address (pr,匹配然后链接到actions/runs/run_id/job/job_id——一次点击直达实时日志。设计为尽力而为best-effort任何查找失败都回退到 run URL评论永远不会比今天更差。这与心跳脚本的body输出中的链接Watch live progress相配合——URL 作为HB_URL传入每个 tick 都会重新渲染。五、实现形态一个脚本两个子命令5.1.github/scripts/autofix-status-heartbeat.sh新增脚本提供两个子命令是整条评论文本唯一事实来源body子命令打印完整的双语 working 状态评论正文marker、round 行、进度行、可折叠中文输入为 round / cap / URL / progress。Post autofix status comment用它做初始发布循环每个 tick 复用它——两者因此永远不会漂移。loop子命令sleep–compose–PATCH 循环直到被 kill 或命中自退出边界。interval/age-cap 覆盖值做正则校验畸形或零值降级到默认值——绝不进入无 sleep 的忙循环。脚本内部还有几处值得注意的细节源码可见set -uo pipefail刻意不加-e(( ... 0 ))钳制在假测试时退出非零是刻意依赖的行为NOW_EPOCH是仅供测试的时钟覆盖生产启动器不设置它因此该值只能来自 env 植入。bash 算术展开会递归求值变量内容植入值里嵌入的命令替换会在这个持 PAT 的进程内部执行——所以只接受纯数字覆盖其余一律回退到真实时钟每次 tick 前用TRUSTED_PATH启动器 stage 时捕获的 PATH重新固定 PATHR6-3 纪律防止同 uid 可写目录中的植入被下一次 tick 解析每个 tick 的 gh 调用使用 af-112 密闭hermetic固定钉死GH_HOSTgithub.com、丢弃GH_TOKEN/GH_ENTERPRISE_TOKEN、在RUNNER_TEMP下每次调用前毫秒级新建、调用后立即删除的GH_CONFIG_DIR。为什么不启动时建一次复用RUNNER_TEMP同 uid 可写跨 tick 复用的目录可被植入一个携带http_unix_socket的config.yml把 PATCH 的 Authorization 头bot PAT投递给植入的监听 socketR11-1已被探针验证——600 秒 sleep 让这个窗口从竞态变成必然。5.2 工作流接线qwen-autofix.yml暂存与摘要Stage trusted schema gate and agent runner~L3903把脚本从可信基线 cp 到RUNNER_TEMP并把 sha256 记录进GITHUB_OUTPUTheartbeat_sha256。注意其rm -rf而非rm -f植入的残留可能是目录rm -f删不掉非零退出会在容错的 cp 之前中止本步骤。启动Post autofix status comment~L5173在env:中通过表达式上下文接收HEARTBEAT_SHA256与TRUSTED_PATH运行前先sha256sum -c校验摘要bash --norc调用body启动时记录heartbeat_pid与heartbeat_start_ticks两个 step outputs。脚本在 agent 之前运行因此不存在跨 agent 执行的 check→use 窗口——bash 在启动时解析整个脚本之后即使暂存副本被换掉也无法改变正在运行的循环。gate 终止Verification gate~L5446在启动任何宿主侧分支代码之前经 step env 收到HB_PID/HB_START_TICKS先touch heartbeat-stop再做生命周期确认的 pid/进程组/会话三级 kill。finalize 终止 排空Finalize autofix status comment~L6842同样 kill并在自己的 PATCH 前读取heartbeat-tick-inflight做排空等待。cleanup 兜底Clean up autofix workdir~L7005再次按同一契约 kill。产物清单Show run artifacts的 echo 列表加入了heartbeat.log整目录 artifact 上传会自动带走死亡或沉默的心跳事后可诊断。5.3 测试与 CI 接线行为测试 .github/scripts/autofix-status-heartbeat.test.mjs1154 行用假gh记录每次调用 argv、可设置GH_FAIL/GH_SLEEP_SECONDS、见证密闭固定与假timeout记录 argv 后直接 exec 尾部让黑洞保护在每台主机上确定性可测覆盖 body 形态、循环 PATCH 节奏、自退出边界、失败容错与 kill 拓扑。工作流接线由 scripts/tests/qwen-autofix-workflow.test.js 钉住start/kill 点位、摘要校验、深链回退、artifact 清单、两个 body 不会漂移等断言。.github/workflows/ci.yml 的HELPER_TESTS注册了autofix-status-heartbeat.test.mjs。六、关键设计决策为什么这么做设计文档的七条决策在实现中逐一得到印证编辑既有状态评论而非发布新评论。一个受管理的 PR 可以跑 100 轮每轮评论堆叠已被 PATCH 纪律防止心跳若发新评论会把数量乘以每轮约 13 次。评论编辑不产生通知、不触发issue_comment事件没有工作流扇出。心跳活在review-addressjob 内而不是 watcher job 或 schedule scan。它需要 comment id、run/job 身份和agent.log——全都在 job 本地。独立 job 必须轮询获取所有这些schedule scan 在本仓库约 40~70 分钟才落地一次并非 cron 暗示的 10 分钟太慢做不了脉冲。PAT 暴露的权衡显式且生命周期有界。心跳在环境中持有 bot PAT——这是周边设计其他部分刻意回避的时间重叠This step holds no PAT。第一版只以agent 沙箱化 fork 代码为界评审证明不够verification gate 随后会在宿主机上以 runner 用户运行分支自己的构建/测试同一 UID 的/proc/pid/environ读取会暴露 token。因此重叠被限定在沙箱化 agent 阶段gate 在任何宿主侧分支代码之前结束循环的整个会话覆盖在途 tick 的timeout 60 gh子树。在该阶段内 token 不落盘、并发的宿主进程都是可信的run-agent.mjs、打包的 CLI且 step 的 gh 调用与每个 tick 都在 af-112 密闭固定下运行。完全避免重叠的替代方案从 schedule scan 或 watcher job 发心跳因节奏与复杂度被否决决策 2权衡已记录在 yml 注释中未来读者能看到这是选择而非疏忽。Kill 目标永不来自 WORKDIR第 2 条纪律的延伸详见前文。以agent.log的 mtime 作为存活信号无流解析、不耦合事件 schema 或 prompt 结构run-agent.mjs已经把每个事件写在那里。粗粒度N 分钟前活跃恰好回答了它是不是死了。内联 kill、暂存启动脚本执行只发生一次在开始时、摘要校验后、agent 之前kill 是内联的因此 PR 分支控制的文件永远不会在持 PAT 或 agent 后的上下文中被执行。10 分钟节奏130 分钟轮次 ≤13 次 PATCH≤20 并发腿——API 负载可忽略卡住的轮次在一个间隔内就会显示出过期的 active 时间戳。七、影响文件与范围边界7.1 影响文件一览文件变更.github/scripts/autofix-status-heartbeat.sh新增bodyloop两个子命令.github/workflows/qwen-autofix.yml暂存并摘要该脚本Post autofix status comment使用它、解析 job 深链、启动循环并记录 pidverification gate 在宿主侧分支代码前 killfinalize / cleanup 从表达式上下文 pid 再次 killartifact 列表新增heartbeat.log.github/workflows/qwen-autofix.md新增 af-149/af-150 设计记录与目录项.github/workflows/ci.yml在HELPER_TESTS注册新的行为测试套件.github/workflows/.size-baseline为 yml 增长更新护栏行scripts/tests/qwen-autofix-workflow.test.js钉住新接线start/kill 点位、摘要校验、深链回退、artifact 列表.github/scripts/autofix-status-heartbeat.test.mjs脚本行为测试body 形态、循环 PATCH 节奏、自退出边界使用假gh7.2 范围边界仅限 review-address 车道。issue 车道不发布状态评论fork-bridge/signal 车道派发进同一个review-addressjob自动被覆盖。刻意排除Out of scope修复被硬性 kill 导致连always()finalize 都没运行的孤儿 working 评论scan 侧 stale-heal更细粒度的里程碑进度已处理 findings i/N实时更新的 commit status。每一项都是独立的价值观判断。八、残余风险已接受设计文档明确承认了三个残余风险实现没有试图工程化绕过存活信号完整性。沙箱可以删除或覆盖heartbeat.pid借身份自检提前结束脉冲、触碰heartbeat-stop、或 bumpagent.log的 mtime 伪造 agent active 0 min ago也可以植入或删除heartbeat-tick-inflight——代价至多是 finalize 的 65s 排空界或重开排空关闭的那个外观性终态覆盖竞态。攻击者只能误标或静默自己这一轮的进度——拿不到 token、没有执行、没有 kill 可达——所以被接受而非绕行。Gate 之后的静默。gate 杀掉循环后评论保持最后一次 tick直到 finalize。深陷 gate/repair 的轮次看起来比实际更安静run 链接仍然存活这是补救手段。跨主机孤儿脉冲。身份自检只在下一轮同 PR 复用孤儿所在主机时才回收崩溃遗留的孤儿池是多主机的、没有每 PR runner 亲和性一般情形下孤儿会通过自己的检查、用陈旧 body 反复 PATCH 共享状态评论直到年龄上限——与活轮的 body 交替出现并在 finalize 后一个间隔内覆盖终态文本。已接受且有真实画像孤儿在/proc/pid/environ中持有 bot PAT 直到上限该宿主机上任何同 uid 进程——包括另一 PR 的轮次在 gate 中运行的宿主侧构建/测试——都能直接读到池的 ptrace scope 限制的是 ptrace attach不是这种读取。上限因此设在 330 分钟 job 信封稍后把孤儿的 token 窗口约束在约一个 job 时长替代方案以 WORKDIR pid 为键的跨运行 kill会重开不可信 kill 目标漏洞。九、未决问题没有阻塞性的未决问题。节奏10 分钟与年龄上限330 分钟 job 信封稍后是对仓库变量友好的常量但当前以字面量随脚本发布直到出现需要配置它们的理由。十、扩展阅读设计文档本体docs/design/autofix-round-heartbeat.md相关设计——runner 级 PAT 隔离与execute/publish拆分docs/design/autofix-gate-runner-isolation.md心跳脚本源码.github/scripts/autofix-status-heartbeat.sh行为测试.github/scripts/autofix-status-heartbeat.test.mjs工作流接线启动、gate kill、finalize 排空、cleanup 兜底.github/workflows/qwen-autofix.yml接线契约测试scripts/tests/qwen-autofix-workflow.test.js双语文档化设计记录af-149/af-150 所在.github/workflows/qwen-autofix.md【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考