feat(autofix): keep the round status comment live during long rounds - #9771
feat(autofix): keep the round status comment live during long rounds#9771wenshao wants to merge 26 commits into
Conversation
A review-address round can run for hours while the PR status comment stays frozen at "working", so on the PR page a healthy long round and a dead one look identical. Start a detached heartbeat loop with the announcement that re-PATCHes the same comment every ~10 min with elapsed time and last agent activity, and deep-link "Watch live progress" to the matrix leg's own live log. The loop lifetime is bounded to the sandboxed agent phase: the verification gate kills it before the first step that runs branch code on the host, and every kill target travels through expression context — WORKDIR is sandbox-writable, so no WORKDIR file is ever read as a kill target. Full rationale in qwen-autofix.md#af-148/af-149 and docs/design/autofix-round-heartbeat.md.
|
Test report (local, pre-merge; full plan in
|
|
Re-run — the diff has moved through 14 review rounds since the first pass, so the gate was re-checked from scratch at the current head. Template looks good ✓ Problem: observed, not theoretical. #9739 round 2 sat ~1.5h with the status comment frozen at "🔄 working" and no way to tell a live round from a dead one short of digging through Actions logs. The before/after is concrete, and the gap is a real operational blind spot for anyone watching a managed PR. Direction: aligned. This is the repo's own autofix visibility — a liveness signal for long silent operations. Editing the existing status comment (never posting new ones) is the right shape: 100 rounds would drown the thread otherwise, and edits raise no issue_comment fan-out. Size: no core production paths touched — the one file under Approach: scope still right. The obvious simpler alternative — a watcher job or the schedule scan pulsing the comment — is rejected in the design doc on cadence grounds (the scan lands every ~40–70 min here, not 10), which matches my read. The kill machinery remains load-bearing on the persistent self-hosted pool: an orphaned PAT-holding loop is genuinely unacceptable there. One drive-by to name: the Risk: no elevated risk signals — none of the changed files match the high-risk path patterns from the revert history. The change is CI-workflow-only; the sensitive surface is the PAT-holding loop itself, which the design doc and the af-148/af-149 records treat head-on (lifetime bounded to the sandboxed agent phase, kill targets never read from sandbox-writable WORKDIR files). Moving on to code review. 🔍 中文说明Re-run——首轮之后该 diff 又经历了 14 轮 review,因此在当前 head 上从头重新过门。 模板完整 ✓ 问题:已观测到,不是理论问题。#9739 第 2 轮约 1.5 小时状态评论冻结在 "🔄 working",除了翻 Actions 日志,没有任何办法区分轮次是活着还是死了。before/after 具体,对盯托管 PR 的人是真实运维盲区。 方向:对齐。这是仓库自身 autofix 的可见性——给长时间静默的操作加活性信号。编辑同一条状态评论(从不新发)是正确的形状:否则 100 轮会淹没帖子,且编辑不触发 issue_comment 扇出。 规模:未触及核心生产路径—— 方案:范围仍然合理。更简单的替代——观察 job 或定时扫描刷新评论——设计文档以节奏为由否决(本仓库扫描实际 40–70 分钟才落地一次,不是 10 分钟),与我的判断一致。终止机制在持久自托管池上仍是承重墙:孤儿式持 PAT 循环在那里真正不可接受。点名一处顺手改动: 风险:无升级风险信号——改动文件均未命中 revert 历史中的高风险路径模式。改动仅涉 CI 工作流;敏感面是持 PAT 的循环本身,设计文档与 af-148/af-149 记录正面处理了它(生命周期限定在沙箱化 agent 阶段、kill 目标从不从 sandbox 可写的 WORKDIR 文件读取)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code review (re-run at the current head)The diff moved through 14 review rounds since the first pass; this is a fresh review of the current state, not a diff-of-diffs. No critical blockers. My independent baseline for this problem was exactly what the PR does — a detached loop PATCHing the same comment with elapsed time + agent.log mtime, killed from an expression-context pid before any host-side branch code — and the implementation exceeds it. What changed since the first review is almost entirely hardening demanded by the review rounds, and it is consistent rather than accretive: the heartbeat's gh calls ride the same af-112 hermetic pins as the sibling PAT steps (per-call minted config dir, planted tokens dropped, pinned host), the launch re-verifies the staged digest immediately before the second, PAT-holding execution (the R8-1 swap window), the finalize body moved into the gate's Minor notes, none blocking:
The lifecycle across steps, since the kill discipline is the heart of the change: sequenceDiagram
participant P1 as Post status step
participant P2 as Heartbeat loop
participant P3 as Status comment
participant P4 as Verification gate
participant P5 as Finalize and cleanup
P1->>P1: verify staged digest, resolve job deep link
P1->>P3: upsert working text rendered by the script
P1->>P2: setsid launch, pid recorded in step output
loop every 10 min
P2->>P3: PATCH same comment with elapsed time and agent activity
end
P4->>P2: touch stop marker, kill group from expression-context pid
P5->>P2: kill again (belt and braces), then wipe workdir
P5->>P3: PATCH terminal text after one round-trip sleep
Note over P2: self-exits on stop marker, replaced pid file, or age cap
Files changed (9)
TestingUnattended CI run — static review plus the PR's own CI below; no PR code was built or executed here.
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 All checks on this head are green and settled — every check completed, no failures. The decisive leg is Not verified: a live pulse in a real round, and no pre-merge lane can settle it — schedule/dispatch resolve workflows from the default branch, so the first real heartbeat fires post-merge by construction, and neither 中文说明代码审查(在当前 head 上重新审查,而非对首轮结论的增量):无关键阻塞。我的独立方案基线(分离循环 PATCH 同一条评论 + agent.log mtime + 表达式上下文 pid 在宿主侧分支代码之前 kill)与 PR 一致,实现更完整。首轮以来的变化几乎全部是 review 轮次要求的加固,且是一致的而非堆砌的:心跳的 gh 调用套用兄弟持 PAT 步骤同款 af-112 hermetic 钉(逐调用现造配置目录、丢弃植入令牌、钉住 host);启动在第二次(持 PAT)执行前立即复验暂存摘要(R8-1 替换窗口);finalize 整体移入 gate 的 env -i 清洁子进程形式(R9-1:BASH_FUNC 导入类被构造性关闭而非逐个枚举);所有 kill 点只从表达式上下文取目标并过数字形状门——从不读取 WORKDIR 文件作为 kill 目标。pid 身份自检(文件必须仍是本循环自己的 pid,而非仅存在)关闭了崩溃轮次孤儿与新一轮在同一评论上交替刷新的问题;畸形或缺失的边界参数退化为默认值而非无 sleep 忙环。三处非阻塞备注: 测试:无人值守 CI 运行——静态审查 + 下方 CI 证据,未在此构建或执行任何 PR 代码。该 head 上所有检查已完成且全绿,无失败。关键腿是 ubuntu Test(运行两个新套件:HELPER_TESTS 里的 26 个行为测试 + vitest 的工作流钉扎块)。macOS/Windows Test 腿是分类跳过(仅工作流改动)而非失败;更早提交上的 Windows 红腿已随后续推送消失。两个 pull_request 工作流运行均以 success 完成。未验证:真实轮次中的实时心跳——合并前任何通道都无法验证(schedule/dispatch 从默认分支解析工作流,首次真实心跳在合并后才会出现; — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — converged review, green CI on the decisive leg, no open blockers; what keeps it from a 5 is the ledger of residual items the review rounds explicitly deferred (bare command words in the always() cleanup kill, the BSD Stepping back: the first pass approved this PR at an earlier head, then the review loop demanded changes, and 14 rounds later the loop itself converged — round 14 found zero new findings and everything remaining was recorded as deferred, "not requested in this round". The honest question for a re-run is whether those rounds improved the change or buried it under accretion. My read: improved. Each round's addition corresponds to a named, probe-verified attack class on the persistent self-hosted pool (PATH plants, staging swaps between digest checks, BASH_FUNC startup imports, planted gh config exfil), and it reuses the doctrine the sibling PAT-carrying steps already carry rather than inventing a new standard. The diff refused the easy exits again: no watcher job, no new API surface, no kill path reading a sandbox-writable file. My independent proposal was the same shape — detached loop, same-comment PATCH, expression-context pid, kill before host-side branch code — and I found no simpler path that survives this pool's threat model; a 10-line Same caveat as the first pass, said again so it isn't lost: the author runs a fleet of autofix-infrastructure PRs; this approval speaks for this one, on its own merits — observed problem, bounded diff, real process-level tests, and records that explain why every kill site exists. CI is settled green on this head (both 中文说明回顾:首轮曾在更早的 head 上批准,随后 review 循环要求修改,14 轮之后循环自身收敛——第 14 轮零新发现,剩余全部记为"延迟处理、本轮不要求"。re-run 的诚实问题是:这些轮次是改进了这个改动,还是把它埋进堆积里。我的判断:改进了。每一轮的增补都对应持久自托管池上一个被点名且经探针验证的攻击类(PATH 植入、摘要检查之间的暂存替换、BASH_FUNC 启动导入、植入的 gh 配置外泄),并且复用的是兄弟持 PAT 步骤已有的准则,而不是发明新标准。这个 diff 再次拒绝了轻松的出口:不加观察 job、不加新 API 面、没有任何 kill 路径读取 sandbox 可写文件。 我的独立方案是同一形状——分离循环、同条评论 PATCH、表达式上下文 pid、宿主侧分支代码之前 kill——没有找到更简单且能在这个池的威胁模型下存活的路径;10 行的 nohup 循环会留下持 bot PAT 的孤儿,正是持久池不能容忍的失败。残余风险被接受并记录在案(sandbox 可以美化自己那一轮的活性数字;崩溃的跨宿主孤儿会脉动到约 5.7 小时年龄上限),而不是以无上限的成本硬去工程化消除——这正是收敛姿态按设计工作。 与首轮相同的提醒,再说一次以免被淹没:作者有一批 autofix 基础设施 PR;本次批准只代表这一个,凭其自身——观测到的问题、有界的 diff、真实进程级测试、解释每个 kill 点为何存在的记录。 该 head 的 CI 已落定全绿(两个 pull_request 工作流运行均 success,包括运行两个新套件的 ubuntu Test 腿),没有需要等待的东西——现在就批准,钉在被审提交上。✅ — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed.
Not explored to full depth (tool budget reached): "agent 5": executing scripts/tests/qwen-autofix-workflow.test.js under vitest ( npm run test:scripts ) — the worktree has no node_modules and npm ci + the prepare bu….
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed。
未探索到全部深度(达到工具调用预算):"agent 5":executing scripts/tests/qwen-autofix-workflow.test.js under vitest ( npm run test:scripts ) — the worktree has no node_modules and npm ci + the prepare bu…。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.22.0)
Review findings on the round heartbeat (PR review of the previous commit): - The orphan self-exit is now an IDENTITY check, not an existence check: WORKDIR is PR-scoped, so the next round recreates heartbeat.pid at the same path, and existence alone let a hard-killed round's orphan pass and keep PATCHing its stale body onto the comment. The loop compares the file against its own pid; removed or replaced ends it. Mirror test added. - run_loop validates every variable a tick needs up front — a launch missing a body var now fails fast instead of producing an immortal loop that never pulses (the exact failure the feature eliminates); the dead HB_START_EPOCH fallback is gone with it. - The deep-link jq filter gains a behavioral oracle (extracted verbatim, run through real jq against paginate-shaped fixtures: cross-page match, comma guard against a shorter PR number, deliberate last choice, empty input), and the timeout wrapper is pinned through a PATH shim proving gh runs under it.
|
@qwen-code /takeover |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent 1a": actionlint could not be run (binary not installed on this host and scripts/lint.js --setup not attempted); the YAML changes were instead verified by reading t….
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
.github/scripts/autofix-status-heartbeat.sh:88 — [review] 12h orphan PAT window — a hard-killed runner's orphan loop holds CI_DEV_BOT_PAT far past the 330-min job timeout.github/workflows/qwen-autofix.yml:4746 — [review] SERVER_URL env re-derives the run-URL prefix RUN_URL already carries — JOB_URL could build on RUN_URL directlydocs/design/autofix-round-heartbeat.md:144 — [review] "no issue_comment events (no workflow fan-out)" claim is wrong — comment-attachment-guard subscribes to editedscripts/tests/qwen-autofix-workflow.test.js:15782 (+2 locations) — [probe] pin suite misses assignment-swap mutants (JOB_URL fallback, HB_ROUND/HB_CAP, HB_WORKDIR/HB_REPO).github/scripts/autofix-status-heartbeat.sh:135 — [probe] PATCH failure discards gh stderr — heartbeat.log records no cause and the start line no repo
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 1a":actionlint could not be run (binary not installed on this host and scripts/lint.js --setup not attempted); the YAML changes were instead verified by reading t…。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Autofix review round summary — PR #9771Addressed both new findings from the automated reviewer's round 2 (both Feedback points and dispositions
Changed files
Verification
中文说明Autofix 审查轮次总结 — PR #9771已处理自动审查者第 2 轮的全部两条新发现(均为 反馈点与处置
变更文件
验证
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:
.github/scripts/autofix-status-heartbeat.sh:39 — [probe] header comment justifies the missing set -e with bash behaviour that does not exist.github/workflows/qwen-autofix.yml:4745 — [probe] deep-link matcher only matches multi-leg matrix job names; single-target runs (41 of 42 real runs) never resolve.github/scripts/autofix-status-heartbeat.test.mjs:392 — [probe] the timeout-shim black-hole test cannot observe the group-kill escape and fails the --foreground fix
Convergence: round 3 posted 3 inline comment(s), 3 of them reported for the first time; the previous round posted 2 (2 new). Findings keep coming back to the same files: .github/scripts/autofix-status-heartbeat.sh (findings in round 2; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 3 轮发布了 3 条行内评论,其中 3 条是首次提出;上一轮发布了 2 条(其中 2 条首次提出)。发现反复回到同一批文件:.github/scripts/autofix-status-heartbeat.sh(第 2 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
…h call Round-3 review findings on the round heartbeat, each reproduced on the pool's host class before the fix: - The killers killed the loop's pid and process group, but each tick's `timeout 60 gh` subtree runs in its OWN process group (coreutils timeout default) under the loop's setsid session — a kill landing mid-tick left it alive holding the PAT for up to 60s (witnessed: group+pid kill leaves the subtree reparented with the canary token in /proc/<pid>/environ). All three killers now also kill the session; the behavioral suite pins both the escape and the fix. - The step's gh calls and every loop tick ran gh without the af-112 hermetic pins, so a planted http_unix_socket in the shared HOME's ~/.config/gh received the tick's Authorization header WITH the PAT (witnessed with the pool's gh). run_loop now mints a fresh GH_CONFIG_DIR and drops planted GH_TOKEN/GH_ENTERPRISE_TOKEN itself, post_status takes the same preamble before its first gh call, and the fail-fast check accepts the step-level GITHUB_TOKEN only. - The default age cap drops from 12h to just past the 330-minute job envelope: only a crash-leftover orphan ever reaches it, and it bounds how long that orphan holds the PAT in /proc/<pid>/environ — readable by any same-UID host process, as a sibling-read probe on this host class confirms (ptrace_scope gates attach, not this read). The af-148 record and the design doc now state that real residual profile instead of "liveness-text corruption only".
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Autofix review-address round summary (PR #9771)Three Critical findings from the automated reviewer (round 3), all claiming [rc:3839002105] Orphan heartbeat loop holds the PAT for up to the 12h cap — RESOLVEDReproduction (this host, kernel 6.8.0-124-generic, ptrace_scope=1): a Fix:
Witnesses: behavioral test pins [rc:3839002107] Mid-tick kill leaves the token-bearing
|
| Finding | Classification | Disposition |
|---|---|---|
| rc:3839002105 (Critical) | Reproduced on this host class | Resolved in code + docs, witnessed |
| rc:3839002107 (Critical) | Reproduced on this host class | Resolved in code + docs, witnessed |
| rc:3839002108 (Critical) | Reproduced on this host class | Resolved in code + docs, witnessed |
| rv:5002839935 "Not linted" | Tool limitation, explicitly not a blocker | No action requested or taken |
| 3 deferred convergence-posture items | Recorded, not requested this round | Left untouched |
Files changed
.github/scripts/autofix-status-heartbeat.sh— hermetic gh pins in
run_loop, GITHUB_TOKEN-only fail-fast check, age cap default 20400,
kill-contract/PAT-note header corrections..github/workflows/qwen-autofix.yml— af-112 preamble in
Post autofix status comment; session kill at the gate (absolute path),
finalize, and cleanup; comments corrected..github/workflows/qwen-autofix.md— af-148 LIFETIME/PAT
TRADE/ORPHAN DISCIPLINE corrected to the witnessed profile.docs/design/autofix-round-heartbeat.md— kill rule, cap derivation,
PAT decision, and residual-risk sections corrected..github/scripts/autofix-status-heartbeat.test.mjs— fake gh records
env channels + slow-tick support; new hermetic-pin witness, GH_TOKEN-only
fail-fast test, session-kill escape/fix witness; cap default updated.scripts/tests/qwen-autofix-workflow.test.js— gate statement list,
finalize/cleanup session-kill pins (+ ordering), script-level pins,
gh-pin loop coverage for the heartbeat step.
Commit: 32ac987a8d fix(autofix): close the heartbeat's token paths at the kill and the gh call
Verification
All commands actually run, in the final committed state unless noted:
- Reproduction probes (pre-fix, this host): sibling
/proc/<pid>/environ
read probe —SIBLING_READ=SUCCESS/DESCENDANT_READ=OK;
mid-tick kill-topology probe with the real script — shipped kill leaves
timeout+child alive with the canary token,pkill -TERM -sempties the
session; gh socket-reroute probe —UNPINNED=EXFIL(Authorization header
with canary at the attacker socket), pinned run no connection. npm run build— passed.npm run typecheck— passed.npm run lint— passed (final state).node --test .github/scripts/autofix-status-heartbeat.test.mjs— 21/21
passed (includes the 3 new witnesses).npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js— 218/218 passed.npm run test:scripts— 61 files, 1669 passed / 16 skipped. The run
reports one unhandled[vitest-worker]: Timeout calling "onTaskUpdate"
RPC artifact; it reproduces identically on the pre-round committed state
(verified by swapping in HEAD's yml + test file) and on this runner only,
so it is pre-existing environmental noise, not caused by this change..github/scripts/check-workflow-size.sh— passed (405676 bytes ≤
404055 baseline + 4096 allowance).npx prettier --checkon all changed files — clean.- Mutation probes (remove/negate → focused suite must FAIL → restore →
green): (1) gate session kill removed → workflow suite failed; (2)
finalize session kill removed → failed; (3) cleanup session kill removed
→ failed; (4) post_status gh pins removed → failed; (5) run_loop pins
removed → hermetic witness failed; (6) age cap reverted to 43200 →
behavioral suite failed; (7) token check restored to accept GH_TOKEN →
both fail-fast tests failed. All restored; final state green. - Integration tests after
npm run bundle: not applicable — the changed
behavior is CI-workflow wiring and a shell script, exercised only by the
node:test/vitest suites above, not by the bundled CLI. npm run generate:settings-schema: not applicable — no settings source
changed.
中文说明
Autofix review-address 轮次总结(PR #9771)
自动审查器(第 3 轮)提出的三条 Critical 发现,均声称当前行为有误且可验证。按流程,每一条都在实现前先在本池宿主机上复现;三次复现结果与审查器的证据一致。三条均已在代码中修复并附带已提交的见证测试。本轮反馈中 "Deferred under the convergence posture"(收敛姿态下延后)列出的三项为已记录但本轮明确不要求处理的内容——未做改动。
[rc:3839002105] 孤儿心跳循环在最长 12 小时上限内持有 PAT —— 已解决
复现(本机,内核 6.8.0-124-generic,ptrace_scope=1): 一个分离的、同 UID 的目标进程在其初始 environ 中持有金丝雀值,被一个非后代的兄弟进程直接读取:SIBLING_READ=SUCCESS(environ 权限 0400,属主可读);对照 DESCENDANT_READ=OK。ptrace_scope 限制的是 ptrace 附加,而非这种直接读取——记录中的前提被证伪,与发现所述一致。
修复:
HB_MAX_AGE_SECONDS默认值从 43200(12 小时)收紧为 20400——略高于 330 分钟作业包络。正常运行轮次的循环会在 gate 或 finalize 处、远早于作业结束被杀死,因此只有崩溃残留的孤儿才会触及该上限;上限现在就是该孤儿 PAT 驻留 environ 窗口的边界。- 修正 af-148(
qwen-autofix.md)与设计文档:跨宿主孤儿的残余风险现在按其真实轮廓被接受(孤儿在/proc/<pid>/environ中持有 bot PAT 直至上限;该宿主上任何同 UID 进程——包括另一 PR 轮次在宿主机侧运行其 gate 的构建/测试——都可直接读取;ptrace_scope 不限制该读取),LIFETIME 段落中被证伪的 "只有池的 ptrace scope 挡在中间" 也已按证据修正。
见证: 行为测试钉住降级默认值 max_age 20400s;工作流测试钉住 HB_MAX_AGE_SECONDS:-20400 与回退值。变异探针:把任一默认值改回 43200 会使行为测试失败;恢复后转绿。
[rc:3839002107] 跳执行期间的 kill 会留下携带令牌的 timeout/gh 子树 —— 已解决
复现(以完全的工作流形状启动真实脚本): 循环 setsid,PID==PGID==SID;在真实 coreutils timeout 下某跳执行期间,该跳子树位于 PGID != 循环 PGID、同一 SID。现网 kill 块(kill -- -PID; kill PID)使 timeout + 子进程存活(重挂到 init),两个存活者的 /proc/<pid>/environ 中均有 GITHUB_TOKEN=CANARY-CI_DEV_BOT_PAT-r4。翻转:pkill -TERM -s <循环pid> → SESSION_EMPTY(会话清空)。
修复: 三个 kill 点(verification gate、finalize、always() cleanup)现在都额外杀掉循环的整个会话——gate 处用 /usr/bin/pkill -TERM -s "${HB_PID}"(按该步骤的绝对路径教义),finalize 与 cleanup 处用 pkill -TERM -s "${HB_PID}"——保留原有组+pid kill 作为双保险。af-148 的 PAT-trade 段落与设计文档决策 3 已修正:重叠之所以在 gate 处结束,是因为 kill 覆盖了整个会话。
见证: 新行为测试同时证明逃逸(第一部分:组+pid kill 使子树存活于会话中)与修复(第二部分:会话级 kill 清空会话),使用真实未打桩的 timeout 与慢速假 gh 让该跳保持在执行中;工作流测试钉住 gate 的精确语句列表以及 finalize/cleanup 的行(含顺序:会话级 kill 在终态 PATCH 之前、在 workdir 清除之前)。变异探针:移除三处会话级 kill 中的任意一行都会使工作流测试失败;逐一恢复后转绿。
[rc:3839002108] 持 PAT 的 gh 调用缺少 af-112 密封钉 —— 已解决
复现(本机 gh 2.23.0,植入配置): 在 ~/.config/gh/config.yml 植入 http_unix_socket 且无钉的情况下,携带金丝雀 PAT 的 gh api user 把 Authorization: token canary-CI_DEV_BOT_PAT-r4 投递到了攻击者 socket(UNPINNED=EXFIL)。采用屋内前置钉(GH_HOST=github.com、unset GH_TOKEN/GH_ENTERPRISE_TOKEN、全新 GH_CONFIG_DIR)后 socket 无连接,gh 直连 api.github.com。
修复:
run_loop()在任何 gh 调用之前自行铸钉:在RUNNER_TEMP下新建GH_CONFIG_DIR(mktemp 失败即快速失败)、钉住GH_HOST、丢弃GH_TOKEN/GH_ENTERPRISE_TOKEN。由于钉会丢弃GH_TOKEN,快速失败检查现在只接受步骤级GITHUB_TOKEN(接受GH_TOKEN会放行一个被钉剥掉凭据的启动——一个永生且永不脉动的循环)。Post autofix status comment在其第一次 gh 调用之前采用同样的 af-112 前置钉,覆盖深链查询、评论 upsert 与循环启动。- 该钉已记录在 af-148 的 PAT-trade 段落:没有它,PAT 无需孤儿存活、无需
/proc读取、无需 kill 失误即可外泄——发生在合法重叠期内,三条权衡论据全都够不着。
见证: 假 gh 现在记录它实际看到的 env;新行为测试以被污染的 GH_HOST/GH_TOKEN/GH_ENTERPRISE_TOKEN/GH_CONFIG_DIR 启动循环,断言每一跳看到的都是钉后的值以及 RUNNER_TEMP 下的全新配置目录。工作流测试的 gh 钉循环现在覆盖 postStatusCommentStep(钉在第一次 gh 调用之前)。变异探针:移除脚本内的钉会使密封见证测试失败;移除步骤前置钉会使工作流测试失败;均已恢复转绿。GH_TOKEN-only 快速失败契约有独立测试(把检查变异回接受 GH_TOKEN 时失败)。
处置一览
| 发现 | 分类 | 处置 |
|---|---|---|
| rc:3839002105(Critical) | 已在本宿主机复现 | 代码+文档修复,附见证 |
| rc:3839002107(Critical) | 已在本宿主机复现 | 代码+文档修复,附见证 |
| rc:3839002108(Critical) | 已在本宿主机复现 | 代码+文档修复,附见证 |
| rv:5002839935 "Not linted" | 工具限制,明确非阻断 | 未要求、未处理 |
| 3 项收敛姿态延后项 | 已记录,本轮不要求 | 未改动 |
变更文件
.github/scripts/autofix-status-heartbeat.sh——run_loop内的密封 gh 钉、仅 GITHUB_TOKEN 的快速失败检查、年龄上限默认 20400、kill 契约/PAT 说明头部注释修正。.github/workflows/qwen-autofix.yml——Post autofix status comment的 af-112 前置钉;gate(绝对路径)、finalize、cleanup 三处会话级 kill;注释修正。.github/workflows/qwen-autofix.md—— af-148 的 LIFETIME/PAT TRADE/ORPHAN DISCIPLINE 按证据修正。docs/design/autofix-round-heartbeat.md—— kill 规则、上限推导、PAT 决策、残余风险各节修正。.github/scripts/autofix-status-heartbeat.test.mjs—— 假 gh 记录 env 通道 + 慢跳支持;新密封钉见证、GH_TOKEN-only 快速失败测试、会话级 kill 逃逸/修复见证;上限默认值更新。scripts/tests/qwen-autofix-workflow.test.js—— gate 语句列表、finalize/cleanup 会话级 kill 钉(含顺序)、脚本级钉、心跳步骤纳入 gh 钉循环。
提交:32ac987a8d fix(autofix): close the heartbeat's token paths at the kill and the gh call
验证
以下为实际执行的命令,除注明外均为最终已提交状态:
- 复现探针(修复前,本机):兄弟进程
/proc/<pid>/environ读取探针——SIBLING_READ=SUCCESS/DESCENDANT_READ=OK;以真实脚本做跳中 kill 拓扑探针——现网 kill 留下携带金丝雀令牌的timeout+子进程,pkill -TERM -s清空会话;gh socket 重定向探针——UNPINNED=EXFIL(攻击者 socket 收到带金丝雀的 Authorization 头),钉后运行无连接。 npm run build—— 通过。npm run typecheck—— 通过。npm run lint—— 通过(最终状态)。node --test .github/scripts/autofix-status-heartbeat.test.mjs—— 21/21 通过(含 3 个新见证)。npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js—— 218/218 通过。npm run test:scripts—— 61 个文件,1669 通过 / 16 跳过。运行报告一个未处理的[vitest-worker]: Timeout calling "onTaskUpdate"RPC 噪声;它在 round 前的已提交状态(换入 HEAD 的 yml + 测试文件验证)与本运行器上同样复现,故为既有的环境噪声,并非本次变更引入。.github/scripts/check-workflow-size.sh—— 通过(405676 字节 ≤ 404055 基线 + 4096 余量)。npx prettier --check(全部变更文件)—— 干净。- 变异探针(移除/取反 → 聚焦测试必须失败 → 恢复 → 转绿):(1) 移除 gate 会话级 kill → 工作流测试失败;(2) 移除 finalize 会话级 kill → 失败;(3) 移除 cleanup 会话级 kill → 失败;(4) 移除 post_status gh 钉 → 失败;(5) 移除 run_loop 钉 → 密封见证失败;(6) 年龄上限改回 43200 → 行为测试失败;(7) 令牌检查恢复为接受 GH_TOKEN → 两个快速失败测试均失败。全部恢复,最终状态转绿。
npm run bundle后的集成测试:不适用——变更行为是 CI 工作流接线与 shell 脚本,仅由上述 node:test/vitest 套件覆盖,不经由打包 CLI 执行。npm run generate:settings-schema:不适用——未改动任何 settings 源。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed.
Not explored to full depth (tool budget reached): "agent test-matrix": full vitest run of scripts/tests/qwen-autofix-workflow.test.js (no node_modules in the review worktree or the parent checkout; monorepo install exceeded the too…; "agent 1c": run scripts/tests/qwen-autofix-workflow.test.js and .github/scripts/autofix-status-heartbeat.test.mjs to confirm green (worktree has no node_modules; vitest…; "agent 1b": none — all planned checks ran to completion..
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 4, not a blocker) — recorded, not requested in this round:
.github/scripts/autofix-status-heartbeat.sh:69 — [probe] portability comment claims date -r FILE works on BSD — BSD date -r takes epoch seconds.github/workflows/qwen-autofix.md:3740 (+1 locations) — [review] two doc sites claim comment edits raise no issue_comment events — edits raise issue_comment:edited and comment-attachment-guard subscribes to it.github/scripts/autofix-status-heartbeat.test.mjs:274 — [probe] no mutation-resistant pin that the tick renders through the shared emit_bodyscripts/tests/qwen-autofix-workflow.test.js:15907 — [probe] sleep-2 pin asserts presence, not position before the terminal PATCHscripts/tests/qwen-autofix-workflow.test.js:15796 — [probe] digest-check-before-use pin covers only the body invocation, not the loop launchscripts/tests/qwen-autofix-workflow.test.js:15771 — [probe] exec-redirect pin secures stdout only; dropping 2>&1 </dev/null hangs the launching stepdocs/design/autofix-round-heartbeat.md:60 (+1 locations) — [review] two doc sites state a 10-minute stream-idle window — run-agent.mjs's default is 20 minutesscripts/tests/qwen-autofix-workflow.test.js:15865 — [probe] HEARTBEAT_PID=$! pin has no adjacency to the setsid launch it recordsscripts/tests/qwen-autofix-workflow.test.js:15869 — [probe] launch env pinned by prefix only for HB_REPO and not at all for HB_WORKDIRdocs/design/autofix-round-heartbeat.md:145 — [probe] swap-immunity rationale cites a false bash property (bash reads scripts incrementally)scripts/tests/qwen-autofix-workflow.test.js:15898 — [probe] finalize/cleanup kill pins match comment-inclusive text — a commented-out kill block ships green
Convergence: round 4 posted 2 inline comment(s), 2 of them reported for the first time; the previous round posted 3 (3 new). Findings keep coming back to the same files: .github/workflows/qwen-autofix.yml (findings in round 3; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查:the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed。
未探索到全部深度(达到工具调用预算):"agent test-matrix":full vitest run of scripts/tests/qwen-autofix-workflow.test.js (no node_modules in the review worktree or the parent checkout; monorepo install exceeded the too…;"agent 1c":run scripts/tests/qwen-autofix-workflow.test.js and .github/scripts/autofix-status-heartbeat.test.mjs to confirm green (worktree has no node_modules; vitest…;"agent 1b":none — all planned checks ran to completion.。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 4 轮,非阻断)——已记录,本轮不要求修改:共 11 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 4 轮发布了 2 条行内评论,其中 2 条是首次提出;上一轮发布了 3 条(其中 3 条首次提出)。发现反复回到同一批文件:.github/workflows/qwen-autofix.yml(第 3 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
Round-4 review findings on the round heartbeat: - [Critical] The staging cp of the heartbeat script and its digest echo carried no guard, but the script is NEW in this PR: the trusted base (pre-merge main) lacks it, so any run that resolves the workflow from the PR's own ref — pull_request labeled/unlabeled on an in-repo takeover-managed PR, or workflow_dispatch on its branch — checks out the trusted base and dies on the bare cp under the runner's -eo pipefail, killing the whole round instead of degrading (witnessed: the extracted stage step exits 1 with "cp: cannot stat" on the merge-base tree). Same rule as the upsert capture in the same step: the cp carries `|| true`, the digest records only when the copy exists, and post_status now guards the empty digest — falling back to the pre-PR inline body (byte-identical to it) and skipping the heartbeat launch instead of digest-checking and running a staged copy that was never staged. Witnessed on the fixed step: exit 0 on the merge-base tree with an empty digest, and the consumer probe renders the pre-PR body there while the digest arm renders the script body. - [Suggestion] The hermetic witness logged every gh-visible credential channel except GITHUB_TOKEN — the loop's sole credential since the round-3 fail-fast. The fake gh shim now logs it and the witness asserts it reaches gh; mutation probe: broadening the loop's unset to drop GITHUB_TOKEN kept the old suite green but fails the new assertion, while in production every tick would fail authentication.
|
🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下: Autofix round summary — PR #9771 (round feedback of 2026-08-23)Two inline findings this round; both addressed and resolved in code (commit [rc:3839541814] [Critical] R4-1 — unguarded staging
|
| Finding | Severity | Disposition |
|---|---|---|
| rc:3839541814 (R4-1) | Critical | Resolved in code — guarded staging + empty-digest degrade, reproduced before and witnessed after |
| rc:3839541819 (R4-2) | Suggestion | Resolved in code — GITHUB_TOKEN channel logged and witnessed, mutation-probed |
Nothing declined, deferred, or escalated this round. The eleven items in the feedback's Deferred under the convergence posture section were recorded by the reviewer as not requested this round and were left untouched.
Verification
Commands actually run this round (all on the touched packages/files):
npm run build— passednpm run typecheck— passednpm run lint— passednpx prettier --checkon the three touched files — passednpx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js— 218 passed (full focused file; run green both after the fix and again on the committed content)node --test .github/scripts/autofix-status-heartbeat.test.mjs— 21 passed (run green on the final and committed content)- Reproduction/witness probes (not repo commands): extracted stage step under
bash --noprofile --norc -eo pipefailon the merge-base tree (exit 1 pre-fix → exit 0 + empty digest post-fix) and on the PR tree (exit 0 + digest); extracted consumer block with empty vs. real digest; four per-guard mutation probes on the workflow pins (each fails its pin) and one mutation probe on the heartbeat loop's unset (fails the new witness).
No settings source changed, so npm run generate:settings-schema was not applicable; no integration harness exercises this workflow YAML beyond the contract test above.
中文说明
Autofix 轮次总结 —— PR #9771(2026-08-23 的轮次反馈)
本轮共两条行内发现,均已在代码中解决(提交 114af68f21)。无需解决基线冲突(--conflict false)。
[rc:3839541814] [Critical] R4-1 —— 新增心跳脚本的暂存 cp 无保护 → 已解决
先复现。 心跳脚本是本 PR 新增的文件,因此可信基线(合并前的 main)中没有它。我从工作流中提取了真实的 stage 步骤,并在合并基线(merge-base)树的检出上以 runner 的 bash --noprofile --norc -eo pipefail 运行:
ARM base-model (merge-base tree, file absent):
cp: cannot stat '.github/scripts/autofix-status-heartbeat.sh': No such file or directory
step_exit=1
ARM pr-tree (control):
step_exit=0, heartbeat_sha256 recorded
任何从 PR 自身 ref 解析工作流的运行(仓内接管托管 PR 的 labeled/unlabeled 事件,或在该分支上的 workflow_dispatch)都会先检出可信基线再做暂存,因此裸 cp 会杀死整个 stage 步骤——这正是同一步骤中紧邻的下方、upsert 内容捕获已经用保护防范的危险。
修复(最小化,遵循同步骤内的先例):
- 暂存
cp现在带2> /dev/null || true,并以注释引用与 upsert 捕获相同的规则。 - 摘要仅在暂存副本存在时记录(
heartbeat_sha256的 echo 外包了if [[ -f ... ]])。 post_status现在对空摘要加了保护:[[ -n "${HEARTBEAT_SHA256}" ]]选择经摘要校验的暂存脚本正文;else分支回退到 PR 前的内联正文——已验证与origin/main上的 PR 前原文逐字节一致——并且心跳启动额外要求暂存摘要存在,因此脚本缺失时降级为旧行为,而不是让本轮失败。
证据:
- 修复后的 stage 步骤重跑:在合并基线树上
step_exit=0,且GITHUB_OUTPUT中没有任何 heartbeat 行(空摘要到达消费端保护);在 PR 树上step_exit=0且摘要正常记录。 - 消费端探针(提取出的正文选择块):空摘要渲染 PR 前内联正文(运行页链接、无 ⏱ 行);真实摘要渲染心跳脚本正文(job 深链 + ⏱ 行)。
- 对新 pin 的逐保护变异探针——分别移除
|| true、移除-f摘要包装、把-n消费端保护替换为if true、以及从启动门中删掉摘要条件,每一次都使工作流 pin 测试失败;恢复修复后通过。这些 pin 在修复前的 YAML 上(修复前)同样失败。
[rc:3839541819] [Suggestion] R4-2 —— 密封性见证漏掉 GITHUB_TOKEN 通道 → 已解决
假 gh shim 的 gh-env.log printf 现在与其他 gh 可见通道一起记录 GITHUB_TOKEN,并且密封性见证断言 GITHUB_TOKEN=fake 到达了 gh——把循环唯一幸存的凭据通道也钉住,而不只是被移除的那些。
变异探针: 把循环的 unset GH_ENTERPRISE_TOKEN GH_TOKEN 扩大到同时移除 GITHUB_TOKEN,修复前的套件保持全绿(21/21——证实了该发现所描述的漏洞),但新断言失败(the step-level GITHUB_TOKEN must reach gh,20/21);恢复后套件重新全绿(21/21)。
处置
| 发现 | 严重度 | 处置 |
|---|---|---|
| rc:3839541814(R4-1) | Critical | 已在代码中解决——有保护的暂存 + 空摘要降级,修复前复现、修复后见证 |
| rc:3839541819(R4-2) | Suggestion | 已在代码中解决——记录并见证 GITHUB_TOKEN 通道,已做变异探针 |
本轮没有拒绝、延后或升级处理任何发现。反馈中"收敛姿态下延后"一节的 11 条已被评审方记录为本轮不要求修改,未做改动。
验证
本轮实际运行的命令(均针对改动的包/文件):
npm run build—— 通过npm run typecheck—— 通过npm run lint—— 通过- 对三个改动文件运行
npx prettier --check—— 通过 npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js—— 218 通过(整个聚焦文件;修复后与提交内容上各跑一次均为绿)node --test .github/scripts/autofix-status-heartbeat.test.mjs—— 21 通过(在最终状态与提交内容上均为绿)- 复现/见证探针(非仓库命令):提取的 stage 步骤在合并基线树上以
bash --noprofile --norc -eo pipefail运行(修复前 exit 1 → 修复后 exit 0 + 空摘要),在 PR 树上(exit 0 + 摘要记录);提取的消费端块分别以空摘要与真实摘要运行;对工作流 pin 的四个逐保护变异探针(每个都使对应 pin 失败),以及对心跳循环 unset 的一个变异探针(使新见证失败)。
未改动 settings 源,因此不适用 npm run generate:settings-schema;除上述契约测试外,没有集成测试覆盖此工作流 YAML。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:
.github/workflows/.size-baseline:37 — [review] ratchet records 404055 but the committed yml is 407083 bytes (off by 3028; both gates green, 1068 bytes of headroom silently bankable by a follow-up)scripts/tests/qwen-autofix-workflow.test.js:15643 — [review] degraded inline fallback body unpinned except its opening line — a future edit dropping the 中文说明 section or mis-ordering printf args ships green and pre-merge rounds post English-…scripts/tests/qwen-autofix-workflow.test.js:15771 — [review] detach pin drops the 2>&1 < /dev/null suffix — removing 2>&1 hangs the post_status step until the 330-min timeout while pin, bash -n and the behavioral suite stay green.github/scripts/autofix-status-heartbeat.test.mjs:300 — [probe] 'sleeps between ticks' discards its waitFor gate result — count <= 5 passes vacuously (0 <= 5) when the loop makes zero PATCH calls (probe: sleep-30 mutant passes at 9518 ms)scripts/tests/qwen-autofix-workflow.test.js:15919 — [probe] finalize/cleanup kill pins drop the load-bearing || true suffix — ESRCH (the normal case) aborts finalize before its terminal PATCH and cleanup before rm -rf; mutant ships green
Convergence: round 5 posted 3 inline comment(s), 3 of them reported for the first time; the previous round posted 2 (2 new). Findings keep coming back to the same files: .github/scripts/autofix-status-heartbeat.test.mjs (findings in round 4; 1 more now); .github/workflows/qwen-autofix.yml (findings in round 4; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)
[Critical] R5-4: The heartbeat launch (post_status, yml:4820-4822) resolves bare setsid and bash — and the PR-added date for START_EPOCH — through the ambient PATH in the PAT-holding step, violating the absolute-path doctrine this same PR applies in the gate block. The loop-side PATH pin of R5-2 does not cover this site (it resolves its command words before the loop exists), and post_status carries no TRUSTED_PATH pin (unlike prepare at yml:3868) while the job's own
中文说明
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 5 轮发布了 3 条行内评论,其中 3 条是首次提出;上一轮发布了 2 条(其中 2 条首次提出)。发现反复回到同一批文件:.github/scripts/autofix-status-heartbeat.test.mjs(第 4 轮已出过发现,本轮又有 1 条);.github/workflows/qwen-autofix.yml(第 4 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)
[Critical] R5-4: The heartbeat launch (post_status, yml:4820-4822) resolves bare setsid and bash — and the PR-added date for START_EPOCH — through the ambient PATH in the PAT-holding step, violating the absolute-path doctrine this same PR applies in the gate block. The loop-side PATH pin of R5-2 does not cover this site (it resolves its command words before the loop exists), and post_status carries no TRUSTED_PATH pin (unlike prepare at yml:3868) while the job's own
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下: AutoFix round report — PR #9771 (review round 6)All four Critical findings (R5-1 … R5-4) were reproduced against the current Feedback dispositions[Critical] R5-1 (rc:3840036405) — existence-gated log read races the [Critical] R5-2 (rc:3840036417) — the tick resolves [Critical] R5-3 (rc:3840036422) — finalize kill block resolves bare [Critical] R5-4 (rv:5003809270, review body) — heartbeat launch resolves Deferred items (recorded under the convergence posture, not requested this Changes
Mutation probes (guard deleted → focused test fails → restored → green)
Verification
Commit: 中文说明AutoFix 轮次报告 —— PR #9771(评审第 6 轮)四条 Critical 发现(R5-1 … R5-4)全部先在当前代码上用探针复现,然后修复,并由新增或收紧的测试见证。每个新守卫都做了变异探针(删除守卫 → 聚焦测试失败 → 恢复 → 变绿)。无冲突( 反馈处置[Critical] R5-1(rc:3840036405)——按"日志存在"门控的读取与空文件窗口竞态 —— 已解决。 复现:把首行日志的 [Critical] R5-2(rc:3840036417)——tick 在持有 PAT 的同时用裸名经可植入的环境 PATH 解析 [Critical] R5-3(rc:3840036422)——finalize 的 kill 块在持有 PAT 的步骤里用裸名解析命令词 —— 已解决。 两臂均复现:(A) 经环境导入的 [Critical] R5-4(rv:5003809270,评审正文)——心跳启动在持有 PAT 的步骤里经环境 PATH 解析裸 延后项(收敛姿态下记录、本轮不要求):保持记录状态。 按本轮范围,均未实现。说明:上方收紧的 finalize pin 已为 kill-pin 项的 finalize 一半带上 变更内容
变异探针(删除守卫 → 聚焦测试失败 → 恢复 → 变绿)
验证
提交: Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 6, not a blocker) — recorded, not requested in this round:
.github/scripts/autofix-status-heartbeat.sh:71 — [review] BSD date -r portability comment is wrong — fallback pins 'active 0 min ago' on macOS dev runs.github/scripts/autofix-status-heartbeat.test.mjs:305 — [review] pacing test discards its waitFor gate — count <= 5 passes vacuously at 0 PATCH callsscripts/tests/qwen-autofix-workflow.test.js:16003 — [probe] reset-step kill pins miss pid-file-read and pattern kills.github/scripts/autofix-status-heartbeat.sh:117 — [probe] BASH_FUNC function-import channel bypasses the TRUSTED_PATH pin (no env -i at loop launch)scripts/tests/qwen-autofix-workflow.test.js:15830 — [probe] digest-before-use ordering pin covers the body invocation only, not the loop launchscripts/tests/qwen-autofix-workflow.test.js:15948 — [probe] finalize kill block has no statement-list pin — bare-word insertion ships greenscripts/tests/qwen-autofix-workflow.test.js:10681 — [probe] PATH-before-first-external pin uses mktemp as proxy — insertion above export passesscripts/tests/qwen-autofix-workflow.test.js:15918 — [probe] HEARTBEAT_PID='' init unpinned — degrade rounds hard-fail under set -uscripts/tests/qwen-autofix-workflow.test.js:15913 — [probe] launch env prefix pins miss HB_WORKDIR/HB_ROUND/HB_CAP — silently heartbeat-less roundscripts/tests/qwen-autofix-workflow.test.js:15788 — [probe] script's PATH re-pin lacks ordering guarantee before mktemp -d
Convergence: round 6 posted 2 inline comment(s), 2 of them reported for the first time; the previous round posted 3 (3 new). Findings keep coming back to the same files: .github/workflows/qwen-autofix.yml (findings in round 5; 2 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 6 轮,非阻断)——已记录,本轮不要求修改:共 10 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 6 轮发布了 2 条行内评论,其中 2 条是首次提出;上一轮发布了 3 条(其中 3 条首次提出)。发现反复回到同一批文件:.github/workflows/qwen-autofix.yml(第 5 轮已出过发现,本轮又有 2 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下: Round summary — PR #9771 (review round 6)Addressed both Critical findings from the automated review; both were Feedback points and decisions[rc:3840742619] R6-1 — planted RUNNER_TEMP leftover becomes the staged heartbeat (Critical) — RESOLVEDClassification: Required — checkable claim, reproduced on the pre-round Reproduction (pre-round code): extracted the real stage step verbatim and Fix: absent-from-base now implies absent-on-disk — Witness: the round's tests run the stage step verbatim against a [rc:3840742631] R6-2 — digest verified once, executed twice; the PAT-holding loop launch has no re-verification (Critical) — RESOLVEDClassification: Required — checkable claim, reproduced on the pre-round Reproduction (pre-round code): extracted the launch block verbatim, Fix: the launch guard now re-verifies the staged script immediately Witness: the round's tests run the launch block verbatim with the digest [rv:5004585743] review body — "Not linted (tool limitation, not a blocker)" — no actionThe review body itself states this is a tool limitation (actionlint Changes
No conflict resolution this round ( Observations for maintainers (not findings addressed this round)
VerificationCommands actually run and their results (working tree at commit ae2c682):
中文说明轮次总结 — PR #9771(评审第 6 轮)已处理自动评审中的两条 Critical 发现;两者都在修复前于旧代码上复现,且每个修复都带有各自的行为见证(behavioral witness)与变异探针(mutation probe)。 反馈点与决定[rc:3840742619] R6-1 — 植入 RUNNER_TEMP 的残留文件成为暂存的 heartbeat 脚本(Critical)— 已解决分类: 必须处理(Required)——可检验的声明,已在旧代码上复现。降级路径在合并前确实是唯一路径(可信基线中没有该脚本,宽容的 复现(旧代码): 逐字提取真实的 stage 步骤,在缺少该脚本的树(合并基线形态)上运行,并在 RUNNER_TEMP 中植入残留文件: 修复: 让"基线中不存在"蕴含"磁盘上不存在"——在 stage 步骤的宽容 见证: 本轮测试在"合并基线形态"的探针树上(带植入残留)逐字运行 stage 步骤,断言步骤退出 0、暂存路径上不残留任何文件、且 [rc:3840742631] R6-2 — 摘要只校验一次、却执行两次;持有 PAT 的 loop 启动没有复校验(Critical)— 已解决分类: 必须处理(Required)——可检验的声明,已在旧代码上复现。loop 启动确实是暂存脚本唯一一次与其摘要校验分离的执行(中间隔着评论 upsert 的两次 gh 往返)。 复现(旧代码): 逐字提取启动块,用原始暂存脚本记录摘要,然后在磁盘上调换该文件,再运行该块: 修复: 启动守卫现在在 见证: 本轮测试用原始副本记录的摘要逐字运行启动块,然后调换文件——断言退出 1 且无攻击者证据文件——并额外断言完好的暂存副本仍能启动(以 fail-fast 消息为证据,无过度阻断)。变异探针:删除复校验行使测试失败;保留该行但去掉 [rv:5004585743] 评审正文 — "未检查(工具限制,非阻断)"— 无需处理评审正文本身说明这是工具限制(actionlint 不支持内嵌 shell 的源映射),不是阻断项;"收敛姿态下延后"一节列出的十条发现已被记录、且明确本轮不要求处理。未对它们做任何实现或拒绝操作,保持已记录状态。 变更
本轮无冲突处理( 给维护者的观察(本轮未作为发现处理)
验证实际执行的命令及结果(工作树位于提交 ae2c682):
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent 5": local execution of scripts/tests/qwen-autofix-workflow.test.js on Linux to independently confirm the green state of the new test on the authoritative lane (CI…; "agent 1a": full npx vitest run scripts/tests/qwen-autofix-workflow.test.js execution (no node_modules installed; behavioral witnesses were replayed standalone instead, a….
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 7, not a blocker) — recorded, not requested in this round:
.github/workflows/qwen-autofix.yml:4845 — [review] comment_id is written only after the launch re-verify — a digest mismatch leaves the posted 'working' comment unflipped by finalize
Convergence: round 7 posted 2 inline comment(s), 2 of them reported for the first time; the previous round posted 2 (2 new). Findings keep coming back to the same files: .github/workflows/qwen-autofix.yml (findings in round 6; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Test (macos-latest, Node 22.x) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"agent 5":local execution of scripts/tests/qwen-autofix-workflow.test.js on Linux to independently confirm the green state of the new test on the authoritative lane (CI…;"agent 1a":full npx vitest run scripts/tests/qwen-autofix-workflow.test.js execution (no node_modules installed; behavioral witnesses were replayed standalone instead, a…。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 7 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 7 轮发布了 2 条行内评论,其中 2 条是首次提出;上一轮发布了 2 条(其中 2 条首次提出)。发现反复回到同一批文件:.github/workflows/qwen-autofix.yml(第 6 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
…ze child R10-1: the gate's heartbeat kill runs in the OUTER shell, which imports every $GITHUB_ENV plant — a BASH_FUNC_builtin%% entry shadows the `builtin` keyword itself, so `builtin kill` there is not sound (the R6-4 doctrine the gate body documents). Switch it to /usr/bin/kill, the same procps already relied on for pkill; `builtin kill` stays where it is sound, inside finalize's env -i clean child. R10-2: the finalize child's gh call carries the PAT but resolved the shared HOME's gh config — pinning HOME's path does not sanitize its contents, which gate-phase host-side branch code (same UID) can write, planting http_unix_socket to capture the Authorization header. Mirror the upsert twin: GH_HOST + RUNNER_TEMP enter the allowlist, and the child mints a hermetic GH_CONFIG_DIR fail-closed before the PATCH. Pins updated: gate statement list and kill-form pins take the absolute-path form, the finalize launch chain gains the two allowlist entries, and the probe env supplies RUNNER_TEMP for the mint.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — consumed all 5 rounds at the cap without converging; residual gaps may remain.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 11, not a blocker) — recorded, not requested in this round:
.github/scripts/autofix-status-heartbeat.sh:137 — [review] HB_INTERVAL_SECONDS:-600 default-expansion channel unpinned — a :-6000 mutant ships green and drops a long round to ~1 pulse (echoes a round-8 deferred entry).github/scripts/autofix-status-heartbeat.test.mjs:319 — [review] no pin ties the parsed interval to the loop's sleep — a sleep 2 mutant ships green through both suites.github/workflows/qwen-autofix.yml:4803 — [review] af-149 deep-link matcher matches a job-name format GitHub never renders (legs render bare) — the deep link never fires and the behavioral oracle only exercises fabricated names
Convergence: round 11 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 3 (3 new). Findings keep coming back to the same files: .github/scripts/autofix-status-heartbeat.sh (findings in round 10; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查:reverse audit — consumed all 5 rounds at the cap without converging; residual gaps may remain。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 11 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 11 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 3 条(其中 3 条首次提出)。发现反复回到同一批文件:.github/scripts/autofix-status-heartbeat.sh(第 10 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下: AutoFix round summary — PR #9771 (address-review)Round outcome: one commit ( Feedback points and dispositions
No feedback was declined, escalated, or deferred to a follow-up this round; no The R11-1 fixRoot cause: the hermetic pin minted ONE Changes (the finding's "shrink mint→use to milliseconds" shape; finalize keeps its already-accepted mint-adjacent-to-use shape):
Witnesses and mutation probes (all caught)
VerificationCommands actually run this round (results for commit
中文说明AutoFix 轮次总结 — PR #9771(address-review)本轮结果:一个提交( 反馈点与处置
本轮没有拒绝、升级或延后到后续 PR 的反馈;无需 R11-1 修复根因:密封钉在 改动(采用该发现建议的"把铸造→使用压缩到毫秒级"形态;finalize 保持其已被认可的"紧邻使用才铸造"形态):
见证与变异探针(全部命中)
验证本轮实际执行的命令(除注明外均为提交
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — consumed all 5 rounds at the cap without converging; residual gaps may remain.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (merge_group-gated) and its macOS execution did not run locally; the changed suite ran green on Linux and its macOS portability was statically audited (bash-3.2 syntax, BSD tooling, capability gating).
Not reviewed: the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 12, not a blocker) — recorded, not requested in this round:
.github/workflows/qwen-autofix.yml:4833 — [probe] first digest check fail-closed only via ambient -e — twin carries explicit || exit 1 (af-023); pin tolerates || true mutant.github/workflows/qwen-autofix.yml:6286 — [probe] finalize fail-closed mint runs ahead of the empty-STATUS_ID early exit — red job for a round that posted no comment.github/workflows/qwen-autofix.yml:4880 — [probe] no test verifies HEARTBEAT_PID=$! is the loop's pid — the value all three killers target.github/workflows/.size-baseline:37 — [probe] ratchet written 528 bytes under the reviewed commit's actual workflow size.github/workflows/qwen-autofix.yml:3618 — [review] sweep comment credits the 24h /tmp age-sweep with ending live orphan loops — impossible (cap ~340 min < sweep 1440 min)docs/design/autofix-round-heartbeat.md:157 (+2 locations) — [review] two committed records falsely claim comment edits raise no issue_comment events — comment-attachment-guard subscribes to edited.github/scripts/autofix-status-heartbeat.sh:77 — [probe] HB_START_EPOCH enters arithmetic expansion unguarded — the invariant enforced for the other three inputs.github/scripts/autofix-status-heartbeat.test.mjs:753 — [probe] failed-mint test uses a fixed 2.5s wall-clock oracle — red on a healthy loop under spawn latency.github/workflows/qwen-autofix.yml:6286 — [probe] finalize child mints the hermetic GH_CONFIG_DIR but never removes it — af-148 promises 'removed right after'scripts/tests/qwen-autofix-workflow.test.js:15853 — [review] moving bilingual/live-link pins to heartbeatScript drops all coverage of the digest-empty fallback bodydocs/design/autofix-round-heartbeat.md:145 — [probe] swap-safety rationale cites a false bash fact — immunity comes from the loop being function-containedscripts/tests/qwen-autofix-workflow.test.js:10764 — [probe] R11-1 wrapper-coverage pin counts only 'gh api' — a bare gh subcommand ships green and reopens the config channel.github/scripts/autofix-status-heartbeat.sh:217 — [probe] kill mid-tick leaves the per-tick gh-config dir unreclaimed — the loop breaks its own af-148 removal disciplinedocs/design/autofix-round-heartbeat.md:60 — [review] design record states a 10-minute stream-idle bound — run-agent.mjs's default is 20 minutes
Convergence: round 12 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 1 (1 new). The rate of new findings is not falling. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had none either, so the next review re-reads the whole diff and will keep doing so until a round's marker carries an anchor again. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查:reverse audit — consumed all 5 rounds at the cap without converging; residual gaps may remain。
未审查:build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (merge_group-gated) and its macOS execution did not run locally; the changed suite ran green on Linux and its macOS portability was statically audited (bash-3.2 syntax, BSD tooling, capability gating)。
未审查:the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 12 轮,非阻断)——已记录,本轮不要求修改:共 14 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 12 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。新发现的产出速度没有下降。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有锚点,因此下一次评审将重读整个 diff——并会一直如此,直到某一轮的标记重新带上锚点。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
…artbeat # Conflicts: # .github/workflows/.size-baseline # .github/workflows/ci.yml
The per-tick hermetic gh-config witness gated waitFor on the call-record count, then read gh-env.log without retry — but the fake gh writes the call record before it appends the env line, so under CPU load the wait passed in the window before the append landed and the assertion saw one line instead of two (19/30 focused runs red under single-core load, all at this assertion). Gate the wait on the env-log line count itself, so the predicate implies what the assertion reads.
|
🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下: Autofix review round — PR #9771Outcome: one Critical inline finding reproduced and fixed; base conflict with Growth audit (required — window over its growth budget)The counting window is over its TEST-line budget (445 vs 400; source 145 vs 400 is under), triggered by the witness tests accumulated while reviewing the heartbeat script across rounds 10–12. Audit verdict:
Feedback dispositions
Conflict resolution (
|
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No code change needed this round — every open finding is already fixed at HEAD; verified, not assumed. The newest review (round 12) posted no finding beyond R12-1, and R12-1's fix is the current HEAD commit. All 27 inline findings open on this PR were re-verified this round against the exact code at HEAD Dispositions — all 27 findings: resolved in code, re-verified this round
Re-verification at HEAD read the exact code for each fix: the heartbeat script (identity check with bounded read, full Acknowledged, not acted on (explicitly non-blocking)
VerificationCommands actually run this round at HEAD
No commit was made this round; the working tree is clean at the pushed head. The 27 resolved findings are listed in 中文说明本轮无需代码改动 —— 所有未关闭的发现均已在 HEAD 修复;已逐条核实,而非凭空断言。 最新一轮评审(第 12 轮)未提出 R12-1 之外的新发现,而 R12-1 的修复正是当前 HEAD 提交。本 PR 上全部 27 条未关闭的行内发现,本轮均已对照 HEAD 处置 —— 全部 27 条发现:已在代码中解决,本轮重新核实
HEAD 上的重新核实逐条读取了确切代码:心跳脚本(有界读取的身份检查、包含 TRUSTED_PATH 的完整 已确认、不处理(明确为非阻断)
验证本轮在 HEAD
本轮未产生提交;工作区在已推送的 head 上保持干净。27 条已解决的发现已列入 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Unresolved, please confirm:
- [Critical] issue comment 5388579025 (dev-bot round-3 feedback summary) — full body not read this round; the defects it names map to inline threads ruled fixed above, but the body itself could not be ruled
- [Critical] 4 entries — full body not read this round; named defects map to inline threads ruled fixed above:
- issue comment 5389896280 (dev-bot round-4/6 feedback summary)
- issue comment 5391337422 (dev-bot round-6 feedback summary)
- issue comment 5409059259 (dev-bot round-8/9 feedback summary)
- issue comment 5413404986 (dev-bot round-9 feedback summary)
Not reviewed: reverse audit — consumed all 5 rounds at the cap without converging; round 5 still surfaced new findings, so residual gaps may remain.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (merge_group-gated) and its macOS execution did not run locally; the changed suite ran green on Linux and its macOS portability was statically audited.
Not reviewed: the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 13, not a blocker) — recorded, not requested in this round:
.github/workflows/qwen-autofix.yml:4833 — [review] first staged-script digest check lacks the explicit || exit 1 its launch-side twin carries (af-023 doctrine).github/workflows/qwen-autofix.yml:6286 — [review] finalize's fail-closed hermetic GH_CONFIG_DIR mint has zero test pairing; deleting it ships green.github/workflows/qwen-autofix.yml:6329 — [review] finalize never removes its minted GH_CONFIG_DIR; contradicts af-148 'removed right after'.github/workflows/qwen-autofix.yml:6286 — [review] finalize's empty-STATUS_ID early exit moved after the fail-closed mint — needless red job on mktemp failurescripts/tests/qwen-autofix-workflow.test.js:10779 — [probe] hermetic_gh rc propagation unpinned — a status-swallowing mutant ships green and kills the PATCH/POST fallbacks.github/workflows/qwen-autofix.yml:6201 — [probe] finalize with absent stage outputs dies 127 at the env -i launch instead of the documented quiet no-op.github/scripts/autofix-status-heartbeat.test.mjs:284 — [probe] first loop test gates on call-file existence but asserts content — mid-write poll reads truncated argv (spurious red).github/workflows/qwen-autofix.yml:4815 — [review] deep-link matcher rides on the unpinned scan-side target-object pr-first key orderscripts/tests/qwen-autofix-workflow.test.js:15873 — [review] bilingual-wrapper pin move dropped the digest-empty fallback body's coverage.github/scripts/autofix-status-heartbeat.sh:75 — [probe] NOW_EPOCH guard accepts leading zeros — octal arithmetic error kills body composition every tickscripts/tests/qwen-autofix-workflow.test.js:16392 — [review] finalize sleep-2 pinned by presence only — relocation after the terminal PATCH ships green.github/scripts/autofix-status-heartbeat.sh:77 — [probe] HB_START_EPOCH unguarded in two arithmetic sites (Critical claim refuted — launcher prefix closes it today; guard + plant test missing).github/workflows/qwen-autofix.yml:4876 — [review] comment_id written only after the launch re-verify — a caught swap strands the posted 'working' comment unflipped
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had none either, so the next review re-reads the whole diff and will keep doing so until a round's marker carries an anchor again. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
未决,请确认:共 5 条(原文未翻译,列表见上方英文部分)。
未审查:reverse audit — consumed all 5 rounds at the cap without converging; round 5 still surfaced new findings, so residual gaps may remain。
未审查:build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (merge_group-gated) and its macOS execution did not run locally; the changed suite ran green on Linux and its macOS portability was statically audited。
未审查:the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 13 轮,非阻断)——已记录,本轮不要求修改:共 13 条(原文未翻译,列表见上方英文部分)。
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有锚点,因此下一次评审将重读整个 diff——并会一直如此,直到某一轮的标记重新带上锚点。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: 🤖 Reviewed the latest feedback — no code change this round. Every point was verified against HEAD instead of assumed. · 已审阅最新反馈——本轮无需改动代码。每一点都在 HEAD 上核实过,而非凭空认定。 Autofix round — PR #9771 (no action)The round-13 review ( Failed check:
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (merge_group-gated) and its suite did not run on macOS locally; the changed suite ran green on Linux and its macOS portability was statically audited (capability-gated launch witness, BSD tooling checks).
Not reviewed: the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed.
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 14, not a blocker) — recorded, not requested in this round:
.github/scripts/autofix-status-heartbeat.sh:144 — [review] PR description still claims a 12-hour age cap; the shipped default is 20400 s (~5.7 h) — body never updated after the review-forced tightening.github/scripts/autofix-status-heartbeat.sh:80 — [review] comment claims date -r FILE works on BSD — BSD date -r takes epoch seconds; dev hosts silently pin 'agent active 0 min ago'.github/workflows/qwen-autofix.yml:6349 — [probe] cleanup kill block uses bare kill/pkill — BASH_FUNC-shadowable, unlike its hardened twins (probe: victim ALIVE under plants)scripts/tests/qwen-autofix-workflow.test.js:16392 — [probe] finalize sleep-2 pinned by presence only — relocation after the terminal PATCH ships green (mutant-proven)scripts/tests/qwen-autofix-workflow.test.js:16168 (+2 locations) — [probe] status-comment MARKER survival unpinned at both composition sites (post_status fallback arm, finalize oracle) — marker-drop mutants ship green.github/scripts/autofix-status-heartbeat.sh:175 — [probe] pid-identity read's no-timeout fallback is an unbounded cat — a planted FIFO stalls the tick forever on such hosts (probe-verified).github/workflows/.size-baseline:21 (+2 locations) — [probe] baseline entries drift from merged sizes — ci.yml recorded 1908 bytes over (banked headroom), qwen-autofix.yml 528 under (allowance)scripts/tests/qwen-autofix-workflow.test.js:10776 — [probe] GH_CONFIG_DIR negative pin matches only the bare export — the house assignment form ships green (mutant-proven).github/workflows/qwen-autofix.yml:3658 — [probe] tolerant cp + existence-only guard digests a partially written staged copy — ENOSPC yields a red step or a blank-comment PATCH (offset sweep).github/workflows/qwen-autofix.yml:6272 — [probe] stop-marker touch follows symlinks in the sandbox-writable WORKDIR — empty-file-create/mtime-bump primitive outside the sandbox view (probe-verified).github/workflows/qwen-autofix.yml:4879 — [probe] post_status lacks the step-level startup-channel pins (BASH_ENV/SHELLOPTS/LD_*) its gate/finalize twins carry — doctrine coverage, no live plant todayscripts/tests/qwen-autofix-workflow.test.js:16348 — [probe] START_EPOCH assignment unpinned — deletion and empty-value mutants ship green; an empty value yields a silent heartbeat-less round
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had none either, so the next review re-reads the whole diff and will keep doing so until a round's marker carries an anchor again. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Test (macos-latest, Node 22.x) was skipped in CI (merge_group-gated) and its suite did not run on macOS locally; the changed suite ran green on Linux and its macOS portability was statically audited (capability-gated launch witness, BSD tooling checks)。
未审查:the executable-script lint — .github/scripts/autofix-status-heartbeat.sh: shellcheck is not installed。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 14 轮,非阻断)——已记录,本轮不要求修改:共 12 条(原文未翻译,列表见上方英文部分)。
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有锚点,因此下一次评审将重读整个 diff——并会一直如此,直到某一轮的标记重新带上锚点。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — non-deterministic tests (flakiness gate) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 577 passed · 0 failed · 577 total Flakiness gate: ❌ 1 of 3 changed test file(s) returned different results across identical re-runs (5 full round(s)) The deterministic flakiness gate re-ran the test files this PR changes and got different outcomes from identical runs (agent verdict: 中文 — 判定:❌ 不通过 · 测试结果不确定(抖动门)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:577 通过 · 0 失败 · 577 总计 抖动门:❌ 1 of 3 changed test file(s) returned different results across identical re-runs (5 full round(s)) 确定性抖动门将本 PR 改动的测试文件原样重跑了多轮,得到了不一致的结果(agent 判定: Verification reportPR #9771 deep verification — autofix round heartbeatVerdict: 中文摘要
ScopeCentral claim: during a round, a detached heartbeat loop keeps the SAME Secondary claims (verified):
Out of scope by budget: live post-merge behavior, the orphan cross-host Central claim — A/B tableBoth arms execute the verbatim step bodies extracted with js-yaml from
Harness: Corrections (to the PR description, not code requests)
FindingsF1 — [Suggestion, PRE-EXISTING infra, not introduced by this PR] the repo's shellcheck lane can never fail
Planted-control: appending an unterminated Suggested fix (for the maintainer; untested here beyond the diagnosis)Run the pipeline under F2 — [Nit]
|
| Mutant | Killed by (intended assertion, quoted failure) | Result |
|---|---|---|
| M1 identity→existence check | "self-exits when the pid file is REPLACED by a newer round" — 'timeout' !== 0 (the pre-R2 defect) |
KILLED |
| M2 clamp fallback removed | "degrades malformed interval and age-cap overrides" — log shows interval abcs max_age 0s instead of 600s/20400s |
KILLED |
M3 NOW_EPOCH numeric guard removed |
"ignores a non-numeric NOW_EPOCH plant instead of evaluating it" — bash arithmetic attempted to evaluate the plant | KILLED |
M4 yml: drop || exit 1 from the pre-launch digest re-check |
pin test — the re-check string pin fails; the swapped-arm witness exists behind it | KILLED |
M5 yml: deep-link last→first |
pin test's jq oracle — expected '10' to be '20' |
KILLED |
| M6 positive control: progress-line text rename | 8 subtests red in the behavioral suite | KILLED (control) |
Controls green: unmutated behavioral suite 26/26 (logs/control-behavioral.log),
unmutated pin test 1/1 (logs/control-pin.log). Survivors: none.
Gates
| Gate | Result |
|---|---|
node --test .github/scripts/autofix-status-heartbeat.test.mjs |
26/26 (logs/behavioral-suite.log, live re-run in evidence/02-behavioral-suite-live.png) |
npx vitest run scripts/tests/qwen-autofix-workflow.test.js scripts/tests/workflow-size.test.js (head) |
414 pass / 1 fail — the fail is upserts deferred findings into a per-PR issue that survives the merge, a pre-existing test (base line 13096) untouched by the diff, failing identically on the base worktree (9 383 ms) and head (9 290 ms) with the same 5 000 ms timeout; environmental on this loaded runner, not attributed to the PR |
| same, base worktree | 413 pass / same 1 fail (head adds exactly +1 test, the heartbeat pin, which passes) |
| HELPER_TESTS wiring | registered in ci.yml env and consumed at both lanes (ci.yml:358, ci.yml:495); the pin test also pins membership |
Size ratchet (workflow-size.test.js) |
195/195; liveness proven: 1-byte baseline mutant → red with the growth message, restored byte-exact |
actionlint (scripts/lint.js --actionlint, pinned v1.7.12) |
clean exit 0; liveness proven: planted bad expression → exit 1 with the exact diagnostic, restored byte-exact |
| shellcheck (pinned v0.11.0) | exit 0; PR's script carries only warning-class findings matching sibling scripts (SC2154 env-contract vars, SC2312) — but see F1: this gate is dead repo-wide |
| yamllint | could not run — pip3 install --user → Permission denied (non-root container); both workflows do parse cleanly under js-yaml |
packages/core/src/telemetry/uiTelemetry.test.ts |
53/53 tests; eslint and prettier --check clean on the file (formatting-only diff, F3) |
Reviewer Test Plan, per step:
- "CI exercises the whole wiring" — verified: suite registered in both
HELPER_TESTS consumers and passing; every named behavior (both activity
states, both clamps, every self-exit bound, sleep-vs-busy-loop bound,
malformed-override degradation, failed-PATCH tolerance) is present as a
test and was executed. The "15 tests" figure is stale (Correction 1). - "Workflow pin test covers staging+digest order, deep-link fallback, launch
gate, four kill sites and kill-target provenance, size-ratchet line" —
verified: pin test passes; staging replayed independently (13/13,
staging-replay.mjs: base-tree absence, head-tree digest, file/dir plant
sweeps); kill sites exercised through the lifecycle A/B (gate slice
verbatim; finalize clean child; cleanup pinned as bare-form "no token"
doctrine — intentional per the pin test's own comment). - "Live behavior (post-merge)" — cannot be verified pre-merge by design;
see Not covered.
Not covered
- Post-merge live runtime (schedule/dispatch resolve the workflow from the
default branch): structurally unverifiable here; the PR declares this
itself. - Per-commit attribution: the checkout is depth-2 (merge commit + base tip
- PR head only); the metadata lists 24 commits but
git rev-list HEAD^1..HEAD^2reaches 1 at the shallow boundary, so only the aggregate
HEAD^1..HEADdiff was verified.
- PR head only); the metadata lists 24 commits but
- yamllint: not installable in this container (pip3 permission); parse-
level YAML validity was checked via js-yaml instead. - Calibration of the step replays against a real emitted artifact: no
GitHub token exists in this sandbox, so no real bot comment was retrievable
to calibrate the post_status/finalize replays. Cross-cell consistency
(degrade arm byte-identical to the base arm; pin suite's own witnesses)
substitutes, but a real prior round's comment would be the stronger
calibrator. The replay reproduces the wire shape, not a live GitHub
round. - Real
gh/API semantics: the oracle is a fakeghencoding the
documented paginate/PATCH shapes — no network calls by design. - Repo-wide ESLint/prettier lanes: not re-run (the PR's own CI covers
them); the one changed TS file was checked individually (gate table). - The shellcheck-lane fix (F1) — pre-existing infra, out of this PR's scope.
Methodology
Environment: the CI verify container (node:22-bookworm, node v22.23.2,
uid 1000, network available; jq/setsid/timeout/pkill/pgrep present;
no shellcheck/zstd system binaries — lint binaries installed via
scripts/lint.js --setup with their sha256 pins). Working tree = merge ref
cdb06dbbd2; base worktree at HEAD^1 (git worktree add tmp/base-tree HEAD^1, removed after the A/B cells were captured — recreate with that
command to re-run the harnesses). package.json/lockfile are untouched by
the PR, and these script tests consume files relative to cwd (no
@​qwen-code/* workspace link involved), so the base control is clean.
ab-heartbeat.mjs parses each tree's workflow with js-yaml, extracts the
Post autofix status comment / Verification gate kill slice / Finalize autofix status comment bodies verbatim, resolves the ${{ }} expressions
the steps use, and runs them under bash --noprofile --norc -eo pipefail —
the exact invocation the docs specify for shell: 'bash' (the workflow's
defaults), which also makes the first (un-|| exit 1'd) digest check
fail-closed under ambient -e. A fake gh records argv and the gh-visible
credential env per call; a delay control file enabled a 3 s PATCH only for
the mid-tick kill phase. staging-replay.mjs extracts the three heartbeat
staging lines verbatim and replays them under the same shell contract on the
base tree, the head tree, and with planted file/directory leftovers. Raw logs
per cell/harness live in logs/; mutants in scratch/ with byte-exact
restore sha256s quoted in logs/mutation-matrix.log.
Flakiness gate log
rounds=5 files=3 skipped=0
file .github/scripts/autofix-status-heartbeat.test.mjs: (cd .) node --test ./.github/scripts/autofix-status-heartbeat.test.mjs
file packages/core/src/telemetry/uiTelemetry.test.ts: (cd packages/core) npx --no-install vitest run ./src/telemetry/uiTelemetry.test.ts
file scripts/tests/qwen-autofix-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/qwen-autofix-workflow.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
.github/scripts/autofix-status-heartbeat.test.mjs: PPFFP
packages/core/src/telemetry/uiTelemetry.test.ts: PPPPP
scripts/tests/qwen-autofix-workflow.test.js: FFFFF
verdict: flaky
summary: 1 of 3 changed test file(s) returned different results across identical re-runs (5 full round(s))
--- per-invocation detail (full copy in the artifact) ---
round 1 · .github/scripts/autofix-status-heartbeat.test.mjs: P (exit 0)
round 1 · packages/core/src/telemetry/uiTelemetry.test.ts: P (exit 0)
round 1 · scripts/tests/qwen-autofix-workflow.test.js: F (exit 1)
--- output tail · round 1 · scripts/tests/qwen-autofix-workflow.test.js ---
��[22m�[39m qwen-autofix workflow�[2m > �[22mwires forced admission end to end: reader, classifier, permission gate, reporter �[33m 537�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mposts the non-main base refusal without depending on any other API call �[33m 2411�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mnarrows the agent prompt after a timeout since the last successful round �[33m 327�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mswitches to Critical-only feedback after five change rounds �[33m 498�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mturns a budget breach into a growth-audit round instead of a divergence stop �[33m 2183�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mposts a takeover milestone digest as rounds accumulate, with a residual bucket �[33m 791�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mrejects a round that expands into CI machinery outside the PR footprint �[33m 2248�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mwrites a gate-authored advisory when a round shrinks test coverage �[33m 982�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22msurfaces deny-by-default footprint expansions, rejecting only when enforcement says so �[33m 825�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mupserts deferred findings into a per-PR issue that survives the merge �[33m 11267�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mbite check: rejects a round whose changed tests pass on the pre-round tree �[33m 3681�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mkeeps the round status comment live with a heartbeat and a job deep link �[33m 708�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mstops a PR that fails to push for CONSECUTIVE_FAILURE_CAP rounds in a row �[33m 802�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mre-arms a stranded PR from a marker instead of a deleted comment �[33m 674�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22maddress-side stale check mirrors the scan-side re-arm logic under bash �[33m 1176�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mbehaviorally posts the re-arm marker only after verifying the PAT identity �[33m 546�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mresolves only the review threads whose findings it implemented �[33m 2965�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22manswers the threads it leaves open, in those threads �[33m 737�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mdoes not flag an API error that appears after a real verdict or a loop guard �[33m 359�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mflags recoverable API renders without a leading status code, and skips non-recoverable ones �[33m 460�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mclassifies permanent API failures terminal and records the cause class �[33m 1406�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mpreserves an agent-written handoff when the budget kills qwen after it �[33m 662�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m growth-audit hardening: park wake set and verdict pipeline (round 3)�[2m > �[22mskips the scan stale-base update while a conflict handoff pends �[33m 748�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mcharges a failure to the round when the baseline is green �[33m 499�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mreports pre-existing only on a matching failure signature, with the baseline transcript as evidence �[33m 458�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mcharges the round when the codes match but the messages differ �[33m 410�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mcrashes verdict-less when the baseline leg breaks the restore (retry, not handoff) �[33m 403�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mshort-circuits before the detach when the head has no failure identity �[33m 434�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mdiscards tracked build dirt so a real verdict survives the restore �[33m 757�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mclassifies an unchanged branch by its verdict files (handoff contract) �[33m 759�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mrejects a handoff written over a dirty workspace, non-retryably �[33m 387�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mnever A/Bs the dist-coupled and stdin-fed checks �[33m 314�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m review verification gate: baseline A/B on deterministic rejection�[2m > �[22mrejects a verdict stream whose later document is truncated or shape-filtered �[33m 359�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m run-agent idle watchdog�[2m > �[22mkills a silent agent at the idle window, naming the idle limit �[33m 1255�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m run-agent idle watchdog�[2m > �[22mnever fires while the agent emits protocol events, however slowly �[33m 3276�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m run-agent idle watchdog�[2m > �[22mnever fires while the agent talks on stderr only �[33m 3264�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m run-agent idle watchdog�[2m > �[22mdoes not treat an unterminated stdout byte stream as progress �[33m 806�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m run-agent idle watchdog�[2m > �[22mrequests streamed partial progress so active headless work refreshes the watchdog �[33m 3261�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m stale sandbox container cleanup�[2m > �[22man idle kill removes only the running sandbox its own agent launched �[33m 1257�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m stale sandbox container cleanup�[2m > �[22ma budget kill removes only the running sandbox its own agent launched �[33m 1266�[2mms�[22m�[39m
�[31m⎯⎯⎯⎯⎯⎯�[39m�[1m�[41m Unhandled Errors �[49m�[22m�[31m⎯⎯⎯⎯⎯⎯�[39m
�[31m�[1m
Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.�[22m�[39m
�[31m⎯⎯⎯⎯⎯⎯�[39m�[1m�[41m Unhandled Error �[49m�[22m�[31m⎯⎯⎯⎯⎯⎯⎯�[39m
�[31m�[1mError�[22m: [vitest-worker]: Timeout calling "onTaskUpdate"�[39m
�[90m �[2m❯�[22m Object.onTimeoutError node_modules/vitest/dist/chunks/rpc.-pEldfrD.js:�[2m53:10�[22m�[39m
�[90m �[2m❯�[22m Timeout._onTimeout node_modules/vitest/dist/chunks/index.B521nVV-.js:�[2m59:62�[22m�[39m
�[90m �[2m❯�[22m listOnTimeout node:internal/timers:�[2m585:17�[22m�[39m
�[90m �[2m❯�[22m processTimers node:internal/timers:�[2m521:7�[22m�[39m
�[31m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯�[39m
�[2m Test Files �[22m �[1m�[32m1 passed�[39m�[22m�[90m (1)�[39m
�[2m Tests �[22m �[1m�[32m220 passed�[39m�[22m�[90m (220)�[39m
�[2m Errors �[22m �[1m�[31m1 error�[39m�[22m
�[2m Start at �[22m 11:12:45
�[2m Duration �[22m 92.75s�[2m (transform 358ms, setup 29ms, collect 427ms, tests 91.83s, environment 0ms, prepare 124ms)�[22m
round 2 · .github/scripts/autofix-status-heartbeat.test.mjs: P (exit 0)
round 2 · packages/core/src/telemetry/uiTelemetry.test.ts: P (exit 0)
round 2 · scripts/tests/qwen-autofix-workflow.test.js: F (exit 1)
--- output tail · round 2 · scripts/tests/qwen-autofix-workflow.test.js ---
m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mposts the non-main base refusal without depending on any other API call �[33m 2395�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mnarrows the agent prompt after a timeout since the last successful round �[33m 376�[2mms�[22m�[39m
�[33m�[2m✓�[22m�[39m qwen-autofix workflow�[2m > �[22mswitches to Critical-only feedback after fiv
...truncated -- full content in the run artifacts.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round — PR #9771 (no action)No code change this round. Every feedback point was classified against HEAD
No failed checks and no still-red checks were reported for this evaluation. 中文说明Autofix 轮次 — PR #9771(无需改动)本轮不改代码。每一条反馈都在 HEAD
本次评估没有报告失败检查,也没有持续变红的检查。传入参数为 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM — re-reviewed at the converged head after the 14-round review loop; looks ready to ship. ✅
qqqys
left a comment
There was a problem hiding this comment.
重新复核当前 head,确认 2 个需要修复的生命周期竞态,详见行内评论。
| # dispatched tick PATCH land before the terminal text goes | ||
| # up. Full rationale → qwen-autofix.md#af-148 | ||
| /usr/bin/touch "${WORKDIR}/heartbeat-stop" 2> /dev/null || true | ||
| if [[ "${HB_PID:-}" =~ ^[0-9]+$ ]]; then |
There was a problem hiding this comment.
[Critical] 这里重复使用的 PID 已经可能失效。Verification gate 早在此步骤之前就用同一个 HB_PID 终止了 heartbeat,后续验证和 repair 最长还会运行很久;到 finalize 时 PID 可能已被系统复用。当前只校验十进制,随后对 PID、进程组和 session 发 TERM,会误杀同一 runner 上的无关进程。等价探针中,将该旧 PID 映射到无关 detached session 后,原样 kill block 确实将其终止。请改成带生命周期确认的 shutdown,不能在后续步骤继续盲杀启动时记录的裸 PID。
| builtin kill -- -"${HB_PID}" 2>/dev/null || true | ||
| builtin kill "${HB_PID}" 2>/dev/null || true | ||
| /usr/bin/pkill -TERM -s "${HB_PID}" 2>/dev/null || true | ||
| /usr/bin/sleep 2 |
There was a problem hiding this comment.
[Critical] 固定等待 2 秒不能保证 terminal PATCH 最后落库。Heartbeat 的 gh 请求允许最长运行 60 秒;杀死本地 client/session 无法撤销服务端已经接收的 PATCH。用真实 heartbeat 脚本和 fake remote 确定性复现:1.67s 接收 WORKING,3.80s 提交 TERMINAL,6.67s 旧请求再提交 WORKING,最终状态重新变成 WORKING。需要显式等待并确认所有在途 heartbeat 更新完成,再写入终态。
…ks before the terminal PATCH Two lifecycle races in the round heartbeat (review findings on QwenLM#9771): 1. The gate/finalize/cleanup killers TERM'd the bare pid recorded at launch — its process group and session too — after validating only that it is decimal. The gate kills the loop up to a whole agent phase before finalize lands, so the pid can be REUSED by then, and the blind block kills an unrelated process (probe-verified: mapped to an unrelated detached session, the block terminated it). Every killer now confirms the pid's /proc/<pid>/stat start time against the launch's capture (heartbeat_start_ticks, field 22) before signaling: a reused pid carries a different start time and a dead pid has no stat, so a failed check kills nothing. 2. Finalize slept a fixed 2s before the terminal PATCH, but killing the client cannot cancel a PATCH the server already accepted (probe-reproduced: WORKING accepted 1.67s in, TERMINAL submitted 3.80s in, the stale WORKING committed 6.67s in and flipped the comment back to live-looking). Each tick now stamps its start epoch into heartbeat-tick-inflight around its 60s-bounded gh call and removes it after; finalize drains until the stamp is absent or older than the 65s completion bound, and only then PATCHes. Both guard paths carry behavioral witnesses (a reused-pid victim must survive the mismatch arm and die on the matching arm; the drain must wait a near-fresh stamp out and proceed at once on an aged one), plus bounded-write/read guards against planted FIFOs on the stamp path. af-148 and the design doc record the doctrine.
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Autofix round summary (PR #9771)Both Critical findings were reproduced on the pre-round code with probes, then fixed at the root cause. One commit: [rv:5030004316] Re-review of the current head — 2 lifecycle racesThe two inline findings below; both addressed. [rc:3862407652] [Critical] Stale
|
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round summary — no action takenOutcome: no change. This round found nothing actionable on PR #9771. Triage
No code change, commit, or conflict resolution was made this round; the branch remains at its previous head. 中文说明Autofix 轮次总结 — 未采取任何操作结果:无变更。 本轮在 PR #9771 上未发现任何可处理的事项。 分类处理
本轮未做任何代码变更、提交或冲突解决;分支保持在原有 head。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
9 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- first staged-script digest check lacks explicit || exit 1 (qwen-autofix.yml:4833) — already reported (round-13 deferral list, review 5024480077)
- hermetic_gh rc-propagation unpinned (qwen-autofix-workflow.test.js:10779) — already reported (round-13 deferral list, review 5024480077)
- bilingual-wrapper pin move dropped the digest-empty fallback body's coverage (qwen-autofix-workflow.test.js:15885) — already reported (round-13 deferral list, review 5024480077)
- NOW_EPOCH guard accepts leading zeros — octal arithmetic error (autofix-status-heartbeat.sh) — already reported (round-13 deferral list, review 5024480077)
- finalize empty-STATUS_ID early exit moved after the fail-closed mint (qwen-autofix.yml:6286) — already reported (round-13 deferral list, review 5024480077)
- comment_id written only after the launch re-verify (qwen-autofix.yml:4876) — already reported (round-13 deferral list, review 5024480077)
- .size-baseline ci.yml entry drifted from merged size — already reported (round-14 deferral list, review 5029530306)
- stop-marker touch follows symlinks in the sandbox-writable WORKDIR — already reported (round-14 deferral list, review 5029530306)
- cleanup kill block bare kill/pkill words (qwen-autofix.yml:6349) — already reported (round-14 deferral list, review 5029530306)
Not reviewed: reverse audit — consumed all 5 rounds at the cap without converging; round 5 still surfaced new findings, so residual gaps may remain.
Not reviewed: build-and-test — packages/cli base-side rerun timed out; settings.test.ts, AuthDialog.test.tsx, test-efficacy.test.ts unmeasured against the merge base (none touched by this diff — path rule).
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Deferred under the convergence posture (round 15, not a blocker) — recorded, not requested in this round:
.github/scripts/autofix-status-heartbeat.sh:186 — [review] pid-identity read bounded in time, not bytes — planted huge file streamed into the PAT-loop's substitution bufferscripts/tests/qwen-autofix-workflow.test.js:16842 — [review] drain witness bounds cannot discriminate a fixed-sleep mutant (all arms green on sleep 2/3)scripts/tests/qwen-autofix-workflow.test.js:16191 — [review] empty-digest arm has no execution witness; arm swap keeps all pins green.github/scripts/autofix-status-heartbeat.sh:158 — [review] interval/max_age guards validate shape, not magnitude — huge plant freezes every self-exit bound.github/scripts/autofix-status-heartbeat.sh:148 — [review] loop WORKDIR writes (log/pid/stamp) follow planted symlinks — host-side truncate primitivedocs/design/autofix-round-heartbeat.md:155 — [review] design doc records a false 'bash parses the whole script at start' doctrinescripts/tests/qwen-autofix-workflow.test.js:16906 — [review] reset-step no-kill pin recognizes only this PR's own kill idioms.github/scripts/autofix-status-heartbeat.test.mjs:52 — [review] fake gh call numbering counts non-call records; lexicographic sort breaks at 8 calls.github/workflows/qwen-autofix.yml:3617 — [review] reset comment overstates orphan reclamation scope (same-PR only, per af-148).github/workflows/qwen-autofix.yml:6329 — [review] written finalize fallback quotes are consumed by the outer shell — written text is not the executed script.github/scripts/autofix-status-heartbeat.test.mjs:190 — [review] NOW_EPOCH witness cannot discriminate an unanchored-regex or fixed-fallback mutant.github/scripts/autofix-status-heartbeat.test.mjs:307 — [review] growing-elapsed-time witness is vacuous (0 >= 0); frozen-counter mutant ships greenscripts/tests/qwen-autofix-workflow.test.js:10864 — [review] PATH-ordering pin cannot see externals placed before the exportscripts/tests/qwen-autofix-workflow.test.js:10779 — [review] hermetic-gh invariant pinned only for the 'gh api' spelling.github/scripts/autofix-status-heartbeat.test.mjs:1 — [test] heartbeat suite outside every npm workspace — enforcement is the hand-maintained HELPER_TESTS list onlyscripts/tests/qwen-autofix-workflow.test.js:45 — [test] workflow-wiring suite enforced only via the root test:scripts chain
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
[Critical] .github/scripts/autofix-status-heartbeat.sh:235 SC2016 — Expressions don't expand in single quotes, use double quotes for that. [lint]
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 9 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:reverse audit — consumed all 5 rounds at the cap without converging; round 5 still surfaced new findings, so residual gaps may remain。
未审查:build-and-test — packages/cli base-side rerun timed out; settings.test.ts, AuthDialog.test.tsx, test-efficacy.test.ts unmeasured against the merge base (none touched by this diff — path rule)。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
收敛姿态下延后(第 15 轮,非阻断)——已记录,本轮不要求修改:共 16 条(原文未翻译,列表见上方英文部分)。
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
[Critical] .github/scripts/autofix-status-heartbeat.sh:235 SC2016 — Expressions don't expand in single quotes, use double quotes for that. [lint]
— qwen3.8-max via Qwen Code /review (v0.22.2)
| const victimTicks = spawnSync( | ||
| 'bash', | ||
| ['-c', `awk '{print $22}' /proc/${victimPid}/stat`], | ||
| { encoding: 'utf8' }, | ||
| ).stdout.trim(); | ||
| expect(victimTicks).toMatch(/^\d+$/); |
There was a problem hiding this comment.
[Critical] R15-1: The new lifecycle-confirmation witness hard-requires Linux procfs with no capability gate, and the drain witness's fresh arm requires unprefixed coreutils timeout — yet the merge_group/schedule/workflow_dispatch-gated test_macos lane collects this suite, because scripts/tests/vitest.config.ts excludes qwen-*-workflow.test.js only on win32. On Darwin /proc/<pid>/stat does not exist, so victimTicks is '' and expect(victimTicks).toMatch(/^\d+$/) fails; the drain fresh arm degrades to an empty stamp and fails its >=800ms bound. The lane is gated, so this PR's check page reports it as skipped — the first red lands only after merge, on the nightly/dispatch lane (the merge queue is not enabled on this repo since 2026-07-02, and ci.yml's own comment says a red nightly on main is treated as a blocker). This file and this same PR gate other Linux-only witnesses on capability (launchWitnessSupported, hasBashMapfile, haveSessionKillTools) — these two witnesses missed the treatment. Note: CI at this head already shows Test (macos-latest, Node 22.x) red, consistent with this.
Witness (verified against local models of Darwin's missing procfs/timeout, declared as such — not a darwin host):
Linux baseline at the reviewed commit: '1 passed | 219 skipped'
absent-procfs shape: awk-exit=2 stdout='[]' -> toMatch(/^\d+$/): FAIL (empty victimTicks)
no-timeout drain shape: {elapsedMs: 8, freshArmBoundsPass: false,
verdict: "fresh arm would FAIL: expect(elapsedMs).toBeGreaterThanOrEqual(800)"}
Suggested fix — gate both witnesses on capability in the file's established shape, e.g. const lifecycleWitnessSupported = spawnSync('bash', ['-c', 'test -r /proc/self/stat && command -v timeout >/dev/null'], { stdio: 'ignore' }).status === 0; wrapping the lifecycle and drain probe blocks, with the string pins left unconditional exactly as the launch witness already does.
Fix witness: removing the capability gate must turn the test_macos lane red again on the cited assertion — that lane is this finding's regression test; please confirm the gate flips on a Darwin runner.
中文说明
新的生命周期确认见证(lifecycle witness)硬依赖 Linux procfs 且未做能力门控,drain 见证的 fresh 分支还需要未带路径前缀的 coreutils timeout——而 merge_group/schedule/workflow_dispatch 门控的 test_macos 腿会收集该测试套件:scripts/tests/vitest.config.ts 只在 win32 上排除 qwen-*-workflow.test.js。Darwin 上没有 /proc/<pid>/stat,victimTicks 为 '',expect(victimTicks).toMatch(/^\d+$/) 失败;drain fresh 分支退化为空 stamp,>=800ms 断言失败。该腿被门控,因此本 PR 的检查页显示为 skipped——第一次变红只会发生在合并之后的 nightly/dispatch 腿(本仓库自 2026-07-02 起未启用 merge queue;ci.yml 注释说明 main 上的 nightly 变红按阻断处理)。本文件和本 PR 的其他 Linux 专属见证都按能力门控(launchWitnessSupported、hasBashMapfile、haveSessionKillTools)——这两个见证漏掉了同样的处理。当前 head 的 CI 中 Test (macos-latest, Node 22.x) 已经变红,与此一致。证据(声明:在 Darwin 缺失 procfs/timeout 的本地模型上验证,并非 darwin 实机)。建议修复:按本文件已有的能力门控形式给两个见证加门控,例如 lifecycleWitnessSupported = spawnSync('bash', ['-c', 'test -r /proc/self/stat && command -v timeout >/dev/null'], ...).status === 0; 包住 lifecycle 与 drain 探针块(字符串 pin 保持无条件,与 launch 见证一致)。修复见证:移除能力门控后,test_macos 腿应在上述断言处重新变红——该腿就是本发现的回归测试。
— qwen3.8-max via Qwen Code /review (v0.22.2)
| const fresh = runDrainArm(() => | ||
| writeFileSync(stampPath, String(Math.floor(Date.now() / 1000) - 64)), | ||
| ); |
There was a problem hiding this comment.
[Critical] R15-2: The drain witness's fresh arm plants the in-flight stamp as floor(W/1000) - 64 at wall time W, so the stamp's true age at write is 64 + (W mod 1000)/1000 seconds. The finalize drain loop breaks once the stamp is >= 65s old, so whenever (W mod 1000) + δ >= 1000 (δ = milliseconds from the write to the first drain check) the loop legitimately breaks on its very first check, and elapsedMs < 800 fails expect(fresh.elapsedMs).toBeGreaterThanOrEqual(800) — the drain behaves exactly as designed while the assertion goes red. Failure shape: W mod 1000 = 900 and δ = 150ms -> the first check sees a stamp that genuinely is 65s old and breaks immediately, elapsedMs ≈ δ. Probability ≈ δ/1000 per run — a few percent on idle runners, tens of percent under CI load — in the Linux pull_request/merge_group gate lane this PR adds the suite to; on hosts without unprefixed timeout(1) the same arm is deterministic red (compounds R15-1).
Witness (probe ran the test's actual fresh arm verbatim — same step extraction, env shape, stamp formula, bounds):
160 iterations -> 2 failures, both immediate breaks at elapsedMs = 9ms
(rate 1.25% on this fast host; all 158 waiting runs landed at 1011-1014ms)
{"mode":"race","N":120,"failuresOfFreshArmBounds":1,"immediateBreaks":1,
"min":9,"max":1013,"failingElapsed":[9]}
Suggested fix — age the fresh stamp so the first check can never satisfy the break: writeFileSync(stampPath, String(Math.floor(Date.now() / 1000) - 63)) — the break then needs NOW_S >= floor(W/1000) + 2, forcing at least one sleep 1 (deterministically >= ~1s, so the 800ms bound holds); keep the < 10000 upper bound and update the '64s old' comment. Gate the arm on command -v timeout for non-Linux hosts as in R15-1.
Fix witness: with the -63 stamp, deleting the drain loop (or replacing it with an immediate break) must turn expect(fresh.elapsedMs).toBeGreaterThanOrEqual(800) red; reverting to - 64 reintroduces the flake — please confirm both mutations.
中文说明
drain 见证的 fresh 分支以墙钟 W 时刻写入 floor(W/1000) - 64 的 stamp,写入时真实年龄为 64 + (W mod 1000)/1000 秒。finalize 的 drain 循环在 stamp 年龄达到 65 秒时 break,因此只要 (W mod 1000) + δ >= 1000(δ 为写入到首次检查的毫秒数),循环就会在第一次检查时合法地 break,此时 elapsedMs < 800,expect(fresh.elapsedMs).toBeGreaterThanOrEqual(800) 失败——drain 行为完全符合设计,变红的是断言本身。触发形状:W mod 1000 = 900、δ = 150ms → 首检即见 65 秒龄 stamp → 立即 break。概率约 δ/1000——空闲 runner 上几个百分点,CI 负载下可达几十个百分点——发生在本 PR 新增套件所在的 Linux pull_request/merge_group 门控腿;在没有未带前缀 timeout(1) 的主机上该分支确定性变红(与 R15-1 叠加)。证据:探针逐字运行了测试真实的 fresh 分支——160 次迭代出现 2 次失败,均为 elapsedMs = 9ms 的立即 break。建议修复:把 fresh stamp 改为 -63,使首次检查永远无法满足 break 条件(必然经过至少一次 sleep 1,800ms 下界确定性成立),保留 < 10000 上界并更新注释;非 Linux 主机按 R15-1 的能力门控处理。修复见证:改为 -63 后,删除 drain 循环(或替换为立即 break)应使 >=800 断言变红;改回 - 64 会重新引入 flake。
— qwen3.8-max via Qwen Code /review (v0.22.2)
| HB_STAT="$(cat "/proc/${HEARTBEAT_PID}/stat" 2>/dev/null)" || HB_STAT='' | ||
| HB_REST="${HB_STAT##*) }" | ||
| HB_FIELDS=(${HB_REST}) | ||
| HEARTBEAT_START_TICKS="${HB_FIELDS[19]:-}" |
There was a problem hiding this comment.
[Critical] R15-3: The launch-side lifecycle reference capture reads the loop's start time through bare shadowable cat, while this same diff's gate twin deliberately uses /usr/bin/cat for the identical read, citing the exact hazard (bare words are shadowed by $GITHUB_ENV-planted BASH_FUNC_<name>%% functions, imported at bash STARTUP even under --norc, ahead of builtins and PATH). The step's export PATH="${TRUSTED_PATH}" closes PATH hijack but not function shadowing. A forged heartbeat_start_ticks travels through expression context to all three killers, which suppress every signal on mismatch — so no kill signals at all, and the loop holds the bot PAT into the Verification gate's host-side build/tests, the exact overlap af-148 declares unacceptable. The suite pins the vulnerable spelling (scripts/tests/qwen-autofix-workflow.test.js:16385), so it ships green. Distinct from the cleanup block's bare cat (which consumes the reference): this site produces it.
Witness (probe — extracted verbatim launch block, staged script digest-verified; killer half ran the gate's actual kill block verbatim against a live victim):
clean arm: captured ticks=315872398 == real start ticks
BASH_FUNC_cat%%: PLANTED_CAT_RAN token=SECRET_PAT,
recorded heartbeat_start_ticks=12345678 (forged; real was 315872467)
forged reference -> victimAliveAfter: true (every kill signal suppressed)
real ticks -> victimAliveAfter: false (kill admitted and landed)
Suggested fix — match the gate twin:
HB_STAT="$(/usr/bin/cat "/proc/${HEARTBEAT_PID}/stat" 2>/dev/null)" || HB_STAT=''
Additionally consider failing closed on an empty capture: kill the just-launched microseconds-old child outright (no reuse window) and export no heartbeat_pid/heartbeat_start_ticks, rather than handing the killers an unusable reference that makes them kill nothing.
Fix witness: the lifecycle pin block around the HB_STAT= assertion must flip from pinning the bare word to asserting the /usr/bin/cat spelling; reverting the /usr/bin/ prefix then turns that assertion red — please confirm with that mutation.
中文说明
启动侧的生命周期参考值捕获通过裸的、可被遮蔽的 cat 读取循环的启动时刻,而同一 diff 的 gate 孪生块对同样的读取刻意使用 /usr/bin/cat,并引用了完全相同的风险(裸命令词会被 $GITHUB_ENV 植入的 BASH_FUNC_<name>%% 函数遮蔽——bash 启动时即使 --norc 也会导入,且优先于内建与 PATH)。该步骤的 export PATH="${TRUSTED_PATH}" 只封闭了 PATH 劫持,封不掉函数遮蔽。伪造的 heartbeat_start_ticks 经由表达式上下文传到全部三个 killer,mismatch 时所有信号都被抑制——没有任何 kill 发出,循环继续持有 bot PAT 进入 Verification gate 的宿主机侧构建/测试,这正是 af-148 声明不可接受的重叠。测试套件把脆弱拼写钉死了(scripts/tests/qwen-autofix-workflow.test.js:16385),因此绿色通过。与 cleanup 块的裸 cat 不同(那里是消费参考值):此处是产生参考值的位置。证据(探针):植入 BASH_FUNC_cat%% 后捕获到伪造的 heartbeat_start_ticks=12345678(真实值为 315872467),伪造参考值下受害者进程在 kill 块后仍存活(全部信号被抑制),真实参考值下 kill 被放行并命中。建议修复:改用 /usr/bin/cat(与 gate 孪生一致);并考虑在捕获为空时失败关闭——直接杀掉刚启动(微秒级,无复用窗口)的子进程且不导出 heartbeat_pid/heartbeat_start_ticks,而不是交给 killer 一个使其什么都不杀的无效参考值。修复见证:HB_STAT= 断言附近的 lifecycle pin 块应从钉裸词改为断言 /usr/bin/cat 拼写;还原该前缀后断言应变红。
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |



What this PR does
A review-address round can run for hours, but the PR's status comment freezes at "🔄 working" the moment the round starts and is not touched again until the round ends — on the PR page, a healthy long round and a dead one look identical. This PR starts a detached heartbeat loop together with the round announcement: every ~10 minutes it edits the SAME status comment (never posts new ones) to add one progress line — elapsed time and last agent activity, derived from the agent log's mtime, bilingual like the rest of the loop's comments. It also deep-links the "Watch live progress" anchor straight to the running matrix leg's live log page instead of the run page, falling back to the run link whenever the lookup fails so the link is never worse than today.
The heartbeat script travels as a trusted-base staged copy with a digest verified from expression context before execution (the same doctrine as the sibling staged scripts), and its kill discipline is built for the persistent runner pool: the loop self-registers its pid, self-exits on a stop marker, a missing pid file, or a 12-hour age cap, and each tick's API call is timeout-wrapped so nothing can live forever or hammer the API.
Why it's needed
Observed on #9739: a round ran ~1.5h before posting anything, and the only way to tell it was alive was to open the Actions run and dig through logs. Maintainers watching a managed PR had no signal to distinguish "working normally" from "stuck". The heartbeat puts a live pulse on the PR itself, and the job deep link lands the reader exactly where the work is visible. The design also documents two security properties it was reviewed against: the loop's PAT lifetime is bounded to the sandboxed agent phase (the verification gate kills it before the first step that runs branch code on the host), and no kill target is ever read from a WORKDIR file — the sandbox mounts the host
/tmpsame-path at the host UID, so those files are treated as untrusted data (kill pids travel through expression context instead).Reviewer Test Plan
How to verify
gh— body rendering in both activity states, both clamps, every self-exit bound, the sleep-vs-busy-loop bound, malformed-override degradation, and failed-PATCH tolerance (15 tests). The workflow pin test covers staging + digest order, the deep-link fallback semantics, the launch gate, all four kill sites and the kill-target provenance, and the size-ratchet line carries the yml growth.Evidence (Before & After)
Before: status comment frozen at "🔄 AutoFix is working on this PR — round 2/100" for the whole round (see #9739 round 2, ~1.5h of silence).
After: the same comment carries
⏱ Running for 42 min · agent active 3 min ago(⏱ 已运行 42 分钟 · agent 最近活动在 3 分钟前), refreshed every ~10 min until the gate.Tested on
Environment (optional)
node --test .github/scripts/autofix-status-heartbeat.test.mjsandnpx vitest run scripts/tests/qwen-autofix-workflow.test.js scripts/tests/workflow-size.test.jsfrom the repo root. No sandbox needed — the suites are self-contained (fakeghon PATH, temp dirs).Risk & Scope
Linked Issues
Observed on #9739 (no closing relation — that PR's round behaved as designed; this one adds the missing visibility).
中文说明
这个 PR 做了什么
一轮 review-address 可以跑几个小时,但 PR 的状态评论在轮次开始的那一刻就冻结在 "🔄 working",直到轮次结束才会再被触碰——在 PR 页面上,一个健康的长轮次和一个死掉的轮次看起来一模一样。本 PR 让轮次公告与一个分离的心跳循环一起启动:每 ~10 分钟编辑同一条状态评论(从不新发评论),追加一行进度——已运行时长与 agent 最近活动时间(取自 agent 日志的 mtime),与循环其他评论一样双语呈现。同时把 "Watch live progress" 锚点深链到正在运行的矩阵腿的实时日志页,而不是运行页;查找失败时回退到运行链接,保证链接永不比现在更差。
心跳脚本以可信基线的暂存副本方式交付,执行前从表达式上下文校验摘要(与兄弟暂存脚本同一教义),其终止纪律按持久 runner 池设计:循环自注册 pid,遇到停止标记、pid 文件消失或 12 小时年龄上限时自退,每跳的 API 调用都包了超时,任何情况下都不会永生或刷爆 API。
为什么需要
在 #9739 上观察到:一轮跑了约 1.5 小时没有任何输出,唯一确认它还活着的办法是打开 Actions 运行翻日志。盯着托管 PR 的维护者没有任何信号来区分"正常工作"与"卡死"。心跳把实时脉搏放到 PR 本身上,深链则让读者直接落到工作可见之处。设计文档还记录了它所经受审查的两条安全属性:循环持 PAT 的生命周期被限定在沙箱化的 agent 阶段(verification gate 在第一个于宿主机执行分支代码的步骤之前杀掉它);kill 目标从不从 WORKDIR 文件读取——sandbox 把宿主
/tmp同路径挂载且以宿主 UID 运行,这些文件被当作不可信数据(kill pid 改走表达式上下文传递)。审查者测试计划
如何验证
gh跑真实脚本——两种活动状态下的正文渲染、两个钳制、全部自退边界、sleep 与忙循环的界定、非法覆盖值的降级、PATCH 失败的容忍(15 个用例)。workflow pin 测试覆盖暂存+摘要顺序、深链回退语义、启动门控、全部四个 kill 点与 kill 目标来源;尺寸棘轮行承载了 yml 的增长。证据(前后对比)
改动前:状态评论整轮冻结在 "🔄 AutoFix is working on this PR — round 2/100"(见 #9739 第 2 轮,约 1.5 小时静默)。
改动后:同一条评论携带
⏱ Running for 42 min · agent active 3 min ago(⏱ 已运行 42 分钟 · agent 最近活动在 3 分钟前),每 ~10 分钟刷新一次直到 gate。测试环境
环境(可选)
仓库根目录执行
node --test .github/scripts/autofix-status-heartbeat.test.mjs与npx vitest run scripts/tests/qwen-autofix-workflow.test.js scripts/tests/workflow-size.test.js。无需 sandbox——套件自包含(PATH 上的假gh、临时目录)。风险与范围
关联 Issue
在 #9739 上观察到(无关闭关系——那个 PR 的轮次行为符合设计;本 PR 补上缺失的可见性)。