fix(autofix): hold autofix rounds while review-pr is in flight - #8899
Conversation
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
Thanks for the PR! Re-run on the author's Template looks good ✓ Problem: observed, not theoretical. #8888 documents the cancellation loop with a concrete ledger from PR #8830 — seven commits in ~9.5 hours, the review-pr run killed and restarted at least six times (two kills from merge-main pushes alone), 83 API calls and ~8.7M input tokens burned on a two-file PR. The root cause is structural: every scan head-mutation is a Direction: aligned. This is the fleet burning its own review work — pure waste removal with no product-surface risk, and the PR deliberately leaves the review workflow's cancellation semantics untouched (a human push still restarts the review immediately). Incremental/delta review remains the long-term direction, tracked separately. Size: no core paths touched — Approach: scope feels right. The gate sits in the scan loop (the only place that can decline a whole round without advancing the feedback watermark), detection is two-layered (rollup liveness + runs-API fallback covering the review workflow's 10-minute delay window), and the ack is human-only with per-review-run dedup. The obvious alternative — re-blocking on the review check via Risk: no elevated risk signals (no high-risk-path matches). Moving on to code review. 🔍 中文说明感谢贡献!应作者 模板完整 ✓ 问题:已观测、非理论问题。#8888 用 PR #8830 的具体账本记录了取消循环——约 9.5 小时内 7 个 commit,review-pr 被杀重启至少 6 次(其中两次由 merge-main push 单独造成),一个两文件 PR 烧掉 83 次 API 调用和约 870 万 input tokens。根因是结构性的:扫描对 head 的每次变更都是 synchronize 事件,而 review workflow 在其上取消正在运行的 review。 方向:对齐。这是舰队在烧掉自己的 review 成果——纯粹的浪费消除,无产品面风险;PR 刻意不改 review workflow 的取消语义(人类 push 仍会立即重启 review)。增量/差量 review 作为长期方向另行跟踪。 规模:未触及核心路径—— 方案:范围合理。门控放在扫描循环内(唯一能在不推进反馈水位线的情况下放弃整轮的位置),探测分两层(rollup 存活检查 + runs-API 回退覆盖 review workflow 的 10 分钟延迟窗口),ack 仅面向人类且按 review run 去重。显见替代方案——经 风险:无升级风险信号(未命中高风险路径)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-reviewed at the current head — this pass verifies the closeout of the round-4 Critical on top of the original design. My independent baseline for #8888 is exactly what this PR implements (per-PR liveness gate in the scan loop, dual detection, whole-round hold without watermark advance, human-only ack), and I found no simpler path it missed. What I checked this round:
Two items from the review ledger remain deliberately open, and I agree with deferring both: R4-2 (the ack path fetches the comment list twice per ack-eligible PR — pure API-efficiency, not correctness) and R5-1 (the infra-rerun hold is broader than push-capable reruns — a one-click rerun of an externally infra-dead check waits out the review; recovery is delayed, never corrupted). Both are named follow-ups, not merge blockers. Test evidenceThe PR's own CI, read via the API — PR code is never executed in triage. All 85 check-runs on the reviewed head are completed with zero failures. The load-bearing one here is
Not verified: live fleet behavior (a real scan actually holding a round while a review is in flight, and the ack landing once per human review). That is observable only post-merge — 中文说明代码审查在当前 head 上重新审查——本轮重点验证 round-4 Critical 的收尾。我对 #8888 的独立方案与本 PR 完全一致(扫描循环内的按 PR 存活门控、双层探测、不推进水位线的整轮暂缓、仅面向人类的 ack),没有发现它遗漏的更简路径。本轮核实:
审查台账中两项刻意未闭环,我同意推迟:R4-2(ack 路径重复抓评论列表——纯 API 效率问题)与 R5-1(infra-rerun 暂缓宽于可推送的重跑——外部检查的一键重跑要多等 review 落地;恢复只被延迟、不被破坏)。均为后续事项,非合并阻断。 测试证据引用 PR 自己的 CI(经 API 读取;triage 从不执行 PR 代码)。被审 head 上全部 85 个 check-run 已完成、零失败。关键一项是 未验证:舰队线上行为(真实扫描在 review 在飞时确实暂缓整轮、ack 每次人类 review 只发一条)。这只能合并后观测——舰队表的 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean closeout of a well-evidenced waste loop; the two open ledger items are named, minor, and correctly deferred. Stepping back: this PR went through the loop the way the loop is supposed to work. The problem was real and expensively proven before any code existed. The first pass approved too early — round 4 then caught a genuine Critical (the infra-rerun path sailing around the new gate), and the fix at this head is exactly right: liveness computed above the rerun block, the rerun conditioned on it, and the behavior pinned in the extracted harness so the next refactor can't silently un-pin it. Round 5's remaining suggestion is a refinement, and the author's call to keep it out of a late-round workflow PR is the right scope discipline — both deferrals are efficiency or recovery-latency, not correctness. Against my independent proposal, the implementation matches and exceeds: the two-layer detection (rollup + delay-window runs fallback), the identity binding down to immutable SHAs and workflow ids, the empty-marker guard on the ack, and the deliberate choice not to touch The one honest reservation, carried from Stage 2: nothing exercises a live scan holding a real round until this merges. The pinned-replay tests are the strongest pre-merge evidence available, and the PR's test plan names the post-merge observables ( Verdict: approve. CI is fully green on the reviewed head and no PR-CI runs are pending. 中文说明置信度:4/5 —— 对一个证据充分的浪费循环的干净收尾;台账上两个未闭环项均已点名、均属次要、推迟得当。 退一步看:这个 PR 走完了循环本该走的路径。问题在任何代码存在之前就已真实且代价高昂地被证明。首轮通过得偏早——round 4 随后抓到一个真正的严重问题(infra-rerun 路径绕过新门控),本 head 的修复恰如其分:存活判定上移到 rerun 块之前、rerun 条件带上它、并用提取块 harness 钉住行为,使下一次重构无法静默拆钉。round 5 剩下的建议是精化,作者把它留在晚轮 workflow PR 之外是正确的范围自律——两个推迟项都是效率或恢复时延问题,不是正确性问题。 对照我的独立方案,实现一致且有超出:双层探测(rollup + 延迟窗口 runs 回退)、身份绑定到不可变 SHA 与 workflow id、ack 的空标记守卫、以及刻意不动 唯一保留意见(承自 Stage 2):合并前没有任何东西演练过真实扫描暂缓真实一轮。钉住回放测试是合并前可得的最强证据,PR 的验证计划也点名了合并后的观测项(舰队表 结论:批准。被审 head 上 CI 全绿,且无进行中的 PR CI。 — 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. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...: none — all planned checks (workflow context, cross-file premises, injection surface, bash semantics, test verification) completed within budget.; PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...: did not run scripts/tests/qwen-autofix-workflow.test.js under vitest** — the worktree and parent checkout have no node_modules , and a full dependency instal…; PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...: none — all checks I started were completed; no unfinished verification remains.; PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...: none — all checks I started were completed within budget..
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
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.
中文说明
未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — all checks above completed within budget.;PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...:none — all planned checks (workflow context, cross-file premises, injection surface, bash semantics, test verification) completed within budget.;PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...:did not run scripts/tests/qwen-autofix-workflow.test.js under vitest** — the worktree and parent checkout have no node_modules , and a full dependency instal…;PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...:none — all checks I started were completed; no unfinished verification remains.;PR #8899 (fix/8888-autofix-review-inflight-gate, fixes #8...:none — all checks I started were completed within budget.。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
未检查(工具限制,非阻断):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.21.10)
| if gh api "repos/${REPO}/actions/runs?head_sha=${PR_HEAD_OID}&per_page=50" \ | ||
| --jq ".workflow_runs[] | select(.workflow_id == ${REVIEW_WF_ID}) | .status" 2> /dev/null \ | ||
| | grep -qE '^(queued|waiting|pending)$'; then |
There was a problem hiding this comment.
[Critical] R1-15: Under this step's -eo pipefail (from defaults.run.shell: bash), this direct gh api … | grep -qE pipeline — the only one in this 5,807-line file — can fail open: grep -q exits on its first match and EPIPE-kills gh mid-write, so pipefail reports the pipeline as failed even though a queued/waiting/pending status was read, and the gate releases while a review is actually live.
Failure scenario: multi-line fallback output is routine — qwen-code-pr-review.yml uses per-run concurrency groups for comment/review/dispatch events, so a /review command or dispatch during a lifecycle run's 10-minute delay window yields two live review runs on the same head. With the match on the first emitted line, grep -q exits, gh dies on SIGPIPE (strace shows per-line unbuffered writes; PIPESTATUS=141), pipefail marks the pipeline failed, and bash evaluates the if as false — silently: no hold message, no fleet row. The scan's stale-base update-branch or address push then fires a synchronize that cancels the in-flight review — precisely the #8888 regression this gate exists to prevent. A live probe measured ~0.4% fail-open at idle and ~11% under CPU load (17/150); the capture-then-grep fix below ran 160/160 clean under the same load.
| if gh api "repos/${REPO}/actions/runs?head_sha=${PR_HEAD_OID}&per_page=50" \ | |
| --jq ".workflow_runs[] | select(.workflow_id == ${REVIEW_WF_ID}) | .status" 2> /dev/null \ | |
| | grep -qE '^(queued|waiting|pending)$'; then | |
| REVIEW_RUN_STATUSES="$(gh api "repos/${REPO}/actions/runs?head_sha=${PR_HEAD_OID}&per_page=50" \ | |
| --jq ".workflow_runs[] | select(.workflow_id == ${REVIEW_WF_ID}) | .status" 2> /dev/null || true)" | |
| if grep -qE '^(queued|waiting|pending)$' <<< "${REVIEW_RUN_STATUSES}"; then |
中文说明
[严重] 在该步骤的 -eo pipefail(来自 defaults.run.shell: bash)下,这条直接的 gh api … | grep -qE 管道 —— 整个 5807 行文件中唯一的一条 —— 可能 fail-open:grep -q 在首个匹配时退出,并在写入中途以 EPIPE 杀死 gh,于是 pipefail 将整条管道报告为失败 —— 即使 queued/waiting/pending 状态已被读到 —— 门控在 review 实际在飞时放行。
失败场景:多行回退输出是常态 —— qwen-code-pr-review.yml 对 comment/review/dispatch 事件使用按 run 的并发组,因此在生命周期 run 的 10 分钟延迟窗口内的一条 /review 命令或 dispatch 会让同一 head 上同时存在两个在飞 review run。当匹配出现在首行时,grep -q 退出,gh 死于 SIGPIPE(strace 显示逐行无缓冲写入;PIPESTATUS=141),pipefail 判定管道失败,bash 将 if 求值为 false —— 且完全静默:没有 hold 消息,没有舰队表行。随后扫描的 stale-base update-branch 或 address push 触发 synchronize,取消在飞 review —— 恰是此门控要防的 #8888 回归。实测探针:空闲时约 0.4% fail-open,CPU 负载下约 11%(17/150);下方的先捕获再 grep 修复在相同负载下 160/160 全部通过。
— qwen3.8-max via Qwen Code /review (v0.21.10)
| REVIEW_STARTED_AT="$(jq -r ' | ||
| [ .[] | ||
| | select((((.status // .state // "") | IN("QUEUED", "IN_PROGRESS", "PENDING", "WAITING", "REQUESTED")) and ((.name // "") == "review-pr"))) |
There was a problem hiding this comment.
[Suggestion] R1-3: The review-pr liveness select-predicate is duplicated verbatim within this added block — once for REVIEW_PR_LIVE above and once here for REVIEW_STARTED_AT — both running over the same CHECKS_JSON in the same iteration, the second only when the first returned true.
Concrete cost: a future edit touching the predicate on one side only (a review-pr job rename, or adding/removing a pending-ish status in the IN(...) list) makes the two disagree — REVIEW_PR_LIVE is true but REVIEW_STARTED_AT comes back empty, so the empty-key branch skips the ack on every scan, silently. The new test does not catch it: it replays only the REVIEW_PR_LIVE filter and pins this second predicate via select(. != "") ] | first // "", which never checks the predicate itself.
Suggested fix: compute both values in one jq invocation so the predicate exists once (e.g. emit the liveness boolean and the first startedAt tab-separated, then split with IFS=$'\t' read).
中文说明
[建议] review-pr 存活判定的 select 谓词在本新增块内逐字重复 —— 一次用于上方的 REVIEW_PR_LIVE,一次用于此处的 REVIEW_STARTED_AT —— 两者在同一次迭代中对同一份 CHECKS_JSON 运行,且后者只在前者为 true 时才执行。
具体代价:未来任何只改一侧谓词的编辑(review-pr job 改名,或在 IN(...) 列表中增删 pending 类状态)都会让两者失配 —— REVIEW_PR_LIVE 为 true 但 REVIEW_STARTED_AT 返回空,于是空 key 分支在每次扫描中都跳过 ack,且完全静默。新测试无法捕获:它只回放 REVIEW_PR_LIVE 过滤器,对此第二个谓词仅以 select(. != "") ] | first // "" 钉住,从不校验谓词本身。
建议修复:在一次 jq 调用中同时算出两个值,使谓词只存在一份(例如以制表符分隔输出存活布尔值与首个 startedAt,再用 IFS=$'\t' read 拆分)。
— qwen3.8-max via Qwen Code /review (v0.21.10)
| if [[ "${SCAN_BOT_ACTOR}" != "${AUTOFIX_BOT}" ]]; then | ||
| echo "::warning::#${PR}: deferred-review ack skipped: PAT authenticates as '${SCAN_BOT_ACTOR}', expected ${AUTOFIX_BOT}" | ||
| else | ||
| gh pr comment "${PR}" --repo "${REPO}" --body "$(printf '🕐 Review received — an automatic review of the current head is still running, so this round is held until it lands (a push now would cancel it and discard its work, #8888). Your feedback stays queued and is addressed in the next round.\n\n<details>\n<summary>中文说明</summary>\n\n🕐 已收到评审 —— 当前 head 上仍有一轮自动 review 在运行,本轮暂缓(现在推送会取消该 review 并丢弃其工作,#8888)。反馈保持排队,将在下一轮一并处理。\n\n</details>\n\n<!-- autofix-review-deferred %s -->' "${REVIEW_STARTED_AT}")" > /dev/null 2>&1 \ |
There was a problem hiding this comment.
[Suggestion] R1-14: This ack promises "addressed in the next round" even when the PR has already hit its round cap — where no next round runs at all until a manual /retry re-arm, and the cap-paused notice that would correct the impression is itself deferred behind this same gate.
Failure scenario: a human reviews a capped bot PR while review-pr is live on the head — the window opens right after the caping push, exactly when humans step in to look at the stalled PR. The review-event scan reaches the gate before any cap determination (the gate never computes ROUND) and posts this unconditional promise. No next round ever comes: the cap block continues unconditionally while ROUND >= EFF_MAX_ROUNDS. The human waits on a promise the bot will not keep, with no hint that /retry is required. This file's own standards treat unkeepable bot promises as defects (see the fork-review decline rationale: a message must not be false for a state the run was never handed).
Suggested fix: with the gate relocated below the cap block (the reorder suggested on the hold branch above — probed safe), skip the ack when ROUND >= EFF_MAX_ROUNDS and let the cap-paused notice speak, or branch this body for capped PRs to name the /retry re-arm command instead of promising a next round.
中文说明
[建议] 此 ack 承诺 "将在下一轮一并处理",即使 PR 已达到轮数上限 —— 那里在手动 /retry 重新武装之前根本不会有下一轮,而本可纠正这一印象的 cap-paused 通知又被同一门控延后。
失败场景:人类在已达上限的 bot PR 上、review-pr 在飞时提交评审 —— 该窗口恰在触发上限的 push 之后打开,正是人类介入查看停滞 PR 的时刻。review 事件扫描在任何轮数判定之前到达门控(门控从不计算 ROUND),发布这条无条件承诺。下一轮永远不会来:上限块在 ROUND >= EFF_MAX_ROUNDS 时无条件 continue。人类等待一个 bot 不会兑现的承诺,且没有任何提示需要 /retry。本文件自身的标准把不可兑现的 bot 承诺视为缺陷(见 fork-review 拒绝理由:消息不应对 run 从未接手的状态为假)。
建议修复:在门控按上方建议移到上限块之后(该重排已实测安全)后,当 ROUND >= EFF_MAX_ROUNDS 时跳过 ack,让 cap-paused 通知说话;或为此处消息体按已上限 PR 分支,指明 /retry 重新武装命令而不是承诺下一轮。
— qwen3.8-max via Qwen Code /review (v0.21.10)
| expect(reviewScanJob).toContain('REVIEW_WF_ID='); | ||
| expect(reviewScanJob).toContain('actions/runs?head_sha=${PR_HEAD_OID}'); | ||
| expect(reviewScanJob).toContain("grep -qE '^(queued|waiting|pending)$'"); |
There was a problem hiding this comment.
[Suggestion] R1-8: The delay-window fallback — the runs-API jq filter, the line-anchored grep, and the -n REVIEW_WF_ID/-n PR_HEAD_OID guards — is asserted by these toContain pins but never executed, unlike the rollup filter immediately above, which is replayed over fixtures in bash.
Failure scenario (probed): mutating .workflow_runs[] to .workflow_runs makes the fallback dead for every status (queued/waiting/pending all → REVIEW_PR_LIVE=false) — reopening the #8888 cancellation window during exactly the 10-minute delay this fallback exists for — yet the full suite passes 150/150 and all three pins survive.
Suggested fix: extract the fallback's --jq filter the same way the rollup filter is extracted above and replay it over canned workflow_runs fixtures: queued/waiting/pending of the review workflow_id → match; completed only, or a different workflow_id → no match.
中文说明
[建议] 延迟窗口回退 —— runs-API jq 过滤器、行锚定 grep、以及 -n REVIEW_WF_ID/-n PR_HEAD_OID 守卫 —— 仅由这些 toContain pin 断言,从未被执行;紧邻上方的 rollup 过滤器则会在 bash 中经夹具回放。
失败场景(已实测):把 .workflow_runs[] 突变为 .workflow_runs 会让回退对所有状态失效(queued/waiting/pending 全部 → REVIEW_PR_LIVE=false)—— 恰在这个回退为之存在的 10 分钟延迟窗口内重新打开 #8888 的取消窗口 —— 而全套件仍 150/150 通过,三个 pin 全部存活。
建议修复:按上方 rollup 过滤器的提取方式提取回退的 --jq 过滤器,并在预制 workflow_runs 夹具上回放:review workflow_id 的 queued/waiting/pending → 匹配;仅 completed,或不同 workflow_id → 不匹配。
— qwen3.8-max via Qwen Code /review (v0.21.10)
wenshao
left a comment
There was a problem hiding this comment.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
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.
中文说明
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— gpt-5.6-sol via Qwen Code /review (v0.21.10)
| | select((((.status // .state // "") | IN("QUEUED", "IN_PROGRESS", "PENDING", "WAITING", "REQUESTED")) and ((.name // "") == "review-pr"))) ] | ||
| | length > 0 |
There was a problem hiding this comment.
[Critical] R2-1: The rollup gate identifies a cancelable automatic review only by the shared, forgeable review-pr job name. An explicit review run or another workflow can expose a live review-pr check on the head; explicit runs use per-run non-canceling concurrency, yet this gate holds the whole autofix round for hours, and a contributor-controlled workflow can forge the same job name to disable managed autofix. Bind the check to the trusted review workflow ID and the automatic pull_request_target event before holding the round.
中文说明
严重问题: R2-1:当前 rollup 门控仅凭共享且可伪造的 review-pr job 名称来判断“会被取消的自动 review”。显式 review 或其他 workflow 都可能在该 head 上产生同名的活动 check;显式 review 使用按 run 隔离且不会因 synchronize 被取消的并发配置,但这里仍会将整个 autofix 轮次阻塞数小时,贡献者控制的 workflow 也可通过同名 job 永久禁用托管 autofix。应在暂缓轮次前将 check 绑定到可信 review workflow ID 以及自动 pull_request_target 事件。
— gpt-5.6-sol via Qwen Code /review (v0.21.10)
| if gh api "repos/${REPO}/actions/runs?head_sha=${PR_HEAD_OID}&per_page=50" \ | ||
| --jq ".workflow_runs[] | select(.workflow_id == ${REVIEW_WF_ID}) | .status" 2> /dev/null \ | ||
| | grep -qE '^(queued|waiting|pending)$'; then |
There was a problem hiding this comment.
[Critical] R2-5: This repository-wide runs query inspects only the first 50 results before filtering by REVIEW_WF_ID. If more than 50 runs exist for the head and the live automatic review is on a later page, the fallback returns false, autofix mutates the head, and synchronize cancels the live review. Use the workflow-specific runs endpoint with pagination, or paginate this endpoint before filtering.
中文说明
严重问题: R2-5:这条仓库级 runs 查询只检查前 50 条结果,之后才按 REVIEW_WF_ID 过滤。如果同一 head 上存在超过 50 个 run,且活动的自动 review 位于后续页面,回退逻辑会错误返回 false,autofix 随后修改 head,并由 synchronize 取消仍在运行的 review。应使用支持分页的 workflow 专用 runs endpoint,或先分页获取完整结果再过滤。
— gpt-5.6-sol via Qwen Code /review (v0.21.10)
| --jq ".workflow_runs[] | select(.workflow_id == ${REVIEW_WF_ID}) | .status" 2> /dev/null \ | ||
| | grep -qE '^(queued|waiting|pending)$'; then |
There was a problem hiding this comment.
[Critical] R2-2: The delay-window fallback omits live workflow-run statuses requested and in_progress. While the automatic review is requested or running prerequisite jobs, review-pr may not exist in the rollup yet; this fallback then returns false, autofix mutates the head, and synchronize cancels the in-flight review. Match the complete nonterminal status family, including requested and in_progress, and cover those states behaviorally.
中文说明
严重问题: R2-2:延迟窗口回退遗漏了活动 workflow run 状态 requested 和 in_progress。自动 review 处于 requested 或正在执行前置 job 时,rollup 中可能尚无 review-pr;此回退会错误返回 false,autofix 修改 head,随后 synchronize 取消正在运行的 review。应匹配完整的非终态集合(包括 requested、in_progress),并添加行为测试。
— gpt-5.6-sol via Qwen Code /review (v0.21.10)
| DEFER_ACKS="$(gh api "repos/${REPO}/issues/${PR}/comments" --paginate --jq '.[].body' 2> /dev/null || true)" | ||
| if grep -qF "<!-- autofix-review-deferred ${REVIEW_STARTED_AT} -->" <<< "${DEFER_ACKS}"; then |
There was a problem hiding this comment.
[Critical] R2-3: Deferred-review acknowledgment dedup trusts a matching marker from any commenter instead of only the autofix bot. A participant can copy the public check startedAt into a hidden marker before a maintainer review; the scan then treats that human comment as bot-owned state and suppresses the promised acknowledgment. Fetch comment authors with bodies and restrict marker matching to ${AUTOFIX_BOT}.
中文说明
严重问题: R2-3:延迟 review 确认评论的去重逻辑信任任意评论者提供的匹配 marker,而不是只信任 autofix bot。参与者可在 maintainer review 前把公开 check 的 startedAt 写入隐藏 marker;扫描随后会将该人类评论当作 bot 状态并抑制承诺的确认回复。应同时获取评论作者和正文,并仅匹配 ${AUTOFIX_BOT} 发布的 marker。
— gpt-5.6-sol via Qwen Code /review (v0.21.10)
|
Request changes — one blocker remains on the delay-window path. [P1] The failure path is:
That leaves the original cancellation race open during the exact window this fallback is meant to cover. Please query live runs for the review workflow and associate them with the PR number, for example via One related follow-up: the message at line 2680 says a later scan will acknowledge a queued review once |
|
Closed the current review-in-flight gate feedback. Changed: the review-pr rollup match is scoped to the review workflow, the delay-window fallback is paginated and no longer uses the grep/pipefail path, fallback runs can supply the ack marker time, and the final defer now runs after the round-cap notice gate. Verified YAML parse, Prettier, git diff check, and a focused static contract check; the full workflow Vitest was blocked in the temp worktree by existing workspace/env issues. CI and automatic review are pending. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not explored to full depth (tool budget reached): "Change summary: PR #8899 adds a review-in-flight gate to…": none — all checks above completed within budget.; "Change summary: PR #8899 adds a review-in-flight gate to…": none — all checks above completed within budget.; "Change summary: PR #8899 adds a review-in-flight gate to…": none — all checks I started completed within budget..
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
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.
中文说明
仅完成部分审查,审查缺口已披露。
未探索到全部深度(达到工具调用预算):"Change summary: PR #8899 adds a review-in-flight gate to…":none — all checks above completed within budget.;"Change summary: PR #8899 adds a review-in-flight gate to…":none — all checks above completed within budget.;"Change summary: PR #8899 adds a review-in-flight gate to…":none — all checks I started completed within budget.。
未审查:反向审计——评审时间预算不足,未能开始第 4 轮。
未检查(工具限制,非阻断):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.21.11)
| // bump this count AND pipe the new site through the normalizer (bumping | ||
| // the count below too) — bumping this pin alone leaves toBe(9) green. | ||
| expect(workflow.split('--paginate').length - 1).toBe(14); | ||
| expect(workflow.split('--paginate').length - 1).toBe(15); |
There was a problem hiding this comment.
[Critical] R2-1: The --paginate site-count pin was bumped 14→15, but this PR adds two paginated fetch sites — the delay-window runs-API fallback (qwen-autofix.yml:2651) and the ack-dedup comments fetch (qwen-autofix.yml:2690). HEAD contains 16 occurrences, so this test fails and the CI test gate is red on this branch. — Failure scenario: any run of npm run test:scripts (chained into test:ci) fails deterministically with AssertionError: expected 16 to be 15 — reproduced at HEAD: 1 failed | 149 passed. The updated comment next to the pin accounts only for the ack-dedup site, which is likely how the second site was miscounted.
| expect(workflow.split('--paginate').length - 1).toBe(15); | |
| expect(workflow.split('--paginate').length - 1).toBe(16); |
Extend the adjacent comment to name both new sites; the normalizer pin toBe(9) correctly stays (neither new site lands in a WORKDIR file).
中文说明
[严重] --paginate 站点计数 pin 从 14→15,但本 PR 新增了两个分页抓取点 —— 延迟窗口 runs-API 回退(qwen-autofix.yml:2651)与 ack 去重评论抓取(qwen-autofix.yml:2690)。HEAD 实际有 16 处,因此该测试失败,本分支的 CI 测试关卡为红。 — 失败场景:任何 npm run test:scripts(被 test:ci 串联执行)都会确定性失败:AssertionError: expected 16 to be 15 —— 已在 HEAD 复现:1 failed | 149 passed。pin 旁更新后的注释只提及了 ack 去重站点,这很可能就是第二个站点被漏数的原因。修复时请同时扩展相邻注释以提及两个新站点;normalizer pin toBe(9) 保持不变(两个新站点都不写 WORKDIR 文件)。
— qwen3.8-max via Qwen Code /review (v0.21.11)
| # check-run yet, but a push now would still cancel it via | ||
| # synchronize. The runs API catches both pull_request_target and |
There was a problem hiding this comment.
[Suggestion] R2-12: The gate holds rounds for review runs a push cannot cancel, and the cancellation rationale in this comment (and in the ack text, both languages) is materially false for that class. qwen-code-pr-review.yml keys concurrency on trigger (lines 51–59): pull_request_target events get the PR-scoped group (cancelable on synchronize), while issue_comment/pull_request_review/pull_request_review_comment/workflow_dispatch events get per-run groups — 'to avoid cancelling active reviews'. Both gate paths are trigger-blind: the runs payload carries .event but the fallback select never consults it, and the rollup matches any live review-pr check. — Failure scenario: a maintainer submits a review → the review workflow fires via pull_request_review (per-run group, uncancellable) and the route fires this scan → the round that would address that feedback is held for the whole run (job-timeout default 360 min + delay + next-scan latency), and the posted ack tells the maintainer 'a push now would cancel it and discard its work' — so they may avoid pushing for hours to 'protect' a review a push cannot touch. The over-hold itself matches the PR's documented batching intent; what survives is the user-facing misinformation.
Suggested fix: restrict the gate to cancellable events (| select((.event // "") == "pull_request_target") + corroborate on the rollup path), or rewrite the comment/ack rationale to the batching truth if holding for all runs is the deliberate choice.
中文说明
[建议] R2-12:门控会为 push 无法取消的 review run 暂缓轮次,而本注释(以及 ack 文案,中英文皆是)中的取消理由对该类 run 是实质性错误的。qwen-code-pr-review.yml 按触发事件决定并发组(51–59 行):pull_request_target 事件进入 PR 级组(可被 synchronize 取消),而 issue_comment/pull_request_review/pull_request_review_comment/workflow_dispatch 事件进入按 run 的组 —— '以避免取消活动的 review'。两条门控路径都不看触发类型:runs 载荷带有 .event,但回退的 select 从不查询它;rollup 则匹配任何存活的 review-pr check。 — 失败场景:maintainer 提交评审 → review workflow 经 pull_request_review 触发(按 run 组,不可取消),route 同时触发本扫描 → 本应处理该反馈的轮次被暂缓整个 run 时长(job 超时默认 360 分钟 + 延迟 + 下次扫描延迟),而发出的 ack 告诉 maintainer '现在推送会取消该 review 并丢弃其工作' —— 他们可能为了避免'伤害'一个 push 根本碰不到的 review 而数小时不推送。暂缓本身符合 PR 文档声明的批处理意图;留下的是面向用户的错误信息。
建议修复:把门控限定到可取消的事件(| select((.event // "") == "pull_request_target") + rollup 路径佐证),或者,若为所有 run 暂缓是刻意的批处理选择,则把注释/ack 的理由改写为批处理的事实。
— qwen3.8-max via Qwen Code /review (v0.21.11)
|
Addressed the remaining R2-1 gap in ee7f327:
All 150 workflow tests pass locally (residual local failures were 5s testTimeout flakes under machine load; they pass with a longer timeout). |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not explored to full depth (tool budget reached): "Change summary: PR #8899 adds a review-in-flight gate to…": none — all checks above completed within budget.; "Change summary: PR #8899 adds a review-in-flight gate to…": none — all checks I planned to do are complete. Tool calls used: about 16..
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/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
仅完成部分审查,审查缺口已披露。
未探索到全部深度(达到工具调用预算):"Change summary: PR #8899 adds a review-in-flight gate to…":none — all checks above completed within budget.;"Change summary: PR #8899 adds a review-in-flight gate to…":none — all checks I planned to do are complete. Tool calls used: about 16.。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
未检查(工具限制,非阻断):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.21.11)
| REVIEW_PR_LIVE="$(jq -r ' | ||
| [ .[] | ||
| | select((((.status // .state // "") | IN("QUEUED", "IN_PROGRESS", "PENDING", "WAITING", "REQUESTED")) and ((.name // "") == "review-pr") and ((.workflowName // "") == "🧐 Qwen Pull Request Review"))) ] |
There was a problem hiding this comment.
[Suggestion] R2-12: The rollup liveness matcher holds the round for ANY live review-pr check regardless of trigger, but explicit-trigger runs (/review via issue_comment / pull_request_review / workflow_dispatch) live in per-run concurrency groups (qwen-pr-review-run-{run_id}, qwen-code-pr-review.yml lines 51-59) that a synchronize push never cancels — the exact case this diff's own runs-API fallback excludes, citing R2-1 (holding the round for one would defer autofix for nothing). Tier 1 over-holds what tier 2 deliberately ignores. — Concrete cost: a maintainer comments @qwen-code /review on a managed PR; while that uncancelable run is live (up to the vars-configured job timeout, ~6h), every scan holds the whole round — skipping the stale-base update AND the fix dispatch for a run the round's push could not cancel; and if a human review event arrives in that window, the ack tells them an automatic review of the current head is still running, which is false for an explicit-trigger run. Suggested fix: when the rollup shows a live review-pr check, consult the runs API (the same query the adjacent fallback branch already issues) and hold only if a matching pull_request_target run exists; or document the over-hold as deliberate.
中文说明
[建议] R2-12:rollup 存活匹配器会对任何在飞的 review-pr check 暂缓整轮,不区分触发源;但显式触发的 run(issue_comment / pull_request_review / workflow_dispatch 的 /review)位于按 run 隔离的并发组(qwen-pr-review-run-{run_id},qwen-code-pr-review.yml 第 51-59 行),synchronize push 永远不会取消它们 —— 恰是本 diff 自己的 runs-API 回退以 R2-1 为由排除的那类(为它暂缓轮次毫无意义)。第一层(rollup)过度暂缓了第二层刻意排除的对象。— 具体代价:maintainer 在托管 PR 上评论 @qwen-code /review;在该不可取消的 run 存活期间(最长为 vars 配置的 job 超时,约 6 小时),每次扫描都暂缓整轮 —— 为一次本轮 push 根本不会取消的 run 跳过 stale-base 更新与修复派发;若该窗口内有人类 review 事件到达,ack 还会告诉对方当前 head 上仍有自动 review 在运行,而对显式触发的 run 这是假的。建议修复:当 rollup 显示在飞 review-pr check 时,查询 runs API(相邻回退分支已发出的同一查询),仅当存在匹配的 pull_request_target run 时才暂缓;或将这种过度暂缓明确注释为有意设计。
— qwen3.8-max via Qwen Code /review (v0.21.11)
There was a problem hiding this comment.
This needs a design decision before code changes. Distinguishing cancelable automatic review check-runs from explicit-trigger review check-runs in statusCheckRollup requires mapping the check back to workflow-run provenance, which is broader than the bounded runs-fallback fix.
|
Closeout for this round: fixed the review-run fallback to fetch one newest runs page per scan, avoid branch-name identity, and tolerate failed/partial fetches by falling back to an empty run set. Resolved the related Criticals and declined test-hardening/notice/fleet expansions rather than growing this PR. Verified the focused workflow tests, Prettier, and diff whitespace; full workflow test hit unrelated 5s harness timeouts, and local actionlint/yamllint did not return before interruption. Two scheduler-boundary findings remain human-gated. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — all checks above were completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed within budget (~13 calls)..
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.
中文说明
未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…":none — all checks above were completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all planned checks completed within budget (~13 calls).。
未检查(工具限制,非阻断):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.21.11)
|
Closeout: fixed the infra-rerun bypass by computing review liveness before failed-job reruns and skipping infra recovery while review-pr is live; also pinned the review workflow display-name invariant. Verified focused workflow tests, Prettier, yamllint, and git diff --check. actionlint was attempted but did not finish locally before interruption, so it is not claimed. Left the duplicate comment-fetch optimization out of this late-round PR. Two earlier scheduling/provenance threads remain human-gated. |
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): "Change summary: PR #8899 adds a review-in-flight gate to…": none — all checks I started were completed within budget..
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.
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"Change summary: PR #8899 adds a review-in-flight gate to…":none — all checks I started were completed within budget.。
未检查(工具限制,非阻断):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.21.11)
|
@qwen-code /resolve |
|
Qwen Code did not run conflict resolution for this request. PR #8899 does not currently have merge conflicts with main. |
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - 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: 738 passed · 1 failed · 739 total 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:738 通过 · 1 失败 · 739 总计 Verification reportPR #8899 Deep Verification ReportVerdict: 中文摘要结论:
ScopeCentral claim: while an automatic review of a managed PR's current head is in flight, the autofix scan holds the whole round — no infra rerun, no stale-base update-branch, no fix dispatch — and the next scan picks it up. Secondary claims: (S1) the runs-API fallback covers the review workflow's 10-minute delay window; (S2) a human review that triggers a held scan gets exactly one ack comment per in-flight review run. Environment: CI verify sandbox ( Central claim — A/B load-bearing proofThe per-PR decision region (from
160/160 scripted assertions pass across the two arms (witness: FindingsF1 (low): duplicate deferred-review ack when liveness source transitions from runs-fallback to rollup — violates the PR's own "at most one ack per review run" invariantThe dedup marker embeds the Reproduced end-to-end in Blast radius: bounded — worst case two acknowledgment comments per review run (after scan 2's ack the rollup-keyed marker exists, so dedup converges); only when a human review lands in the delay window and another review event lands before the review concludes. No feedback loss (watermark untouched), no canceled review, no state corruption. Severity is low; it contradicts the description's "one acknowledgment comment per in-flight review run" and the test's own comment ("one visible acknowledgment per in-flight review run"), so it is reported rather than waved through. Measured candidate fix (scratch copy only; PR untouched —
The diff (two hunks in Notes (not findings)
CorrectionsNone — no prior review round or bot claim needed correcting (first round; no Gates and vacuity
The new test is not vacuous: every guard the PR introduces is pinned (matrix above, no survivors), the behavioral replays execute the real extracted jq (they flip under M3/M4 mutations), and the positive control proves the suite can fail. Not covered
MethodologyOne container (the lane's own Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Closeout summary: no code changes. Resolved the new infra-rerun narrowing suggestion as follow-up outside this review-cancellation guard. One earlier scheduling-boundary thread remains human-gated. Checks remain as reported by GitHub. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Released in v0.21.12. |
…me base refresh A synchronize used to cancel-in-progress the in-flight review run no matter how close to done it was: on PR #9729 a push cancelled run 32726618419 4h06m in, minutes from posting, and cancelled review runs wasted ~7h41m of runner time across that one PR. A declarative cancel cannot weigh the run's progress, so the supersede decision moves into the run: cancel-in-progress now fires on closed only, a synchronize run queues pending in the PR-scoped group, and the in-flight run's new supersede watcher either ends the attempt early (pre-threshold: the queued replacement reviews the new head from scratch, within a poll interval) or, past the salvage threshold (compose artifact present, or elapsed >= 50% of the size-aware budget, tunable via QWEN_REVIEW_SALVAGE_ELAPSED_PERCENT), finishes and posts against the head it reviewed. The gh-wrapper write guard gains a salvage escape pinned to the reviewed head; the CLI's existing presubmit head-drift gate annotates and caps the verdict, and a new step posts a historical-head note naming the anchor the next incremental review starts from. The delay job now also skips a queued run whose event head went stale while it waited. The other half covers the one loop-owned head move outside the #8888/#8899 dispatch hold: the autofix report step's stale-base update-branch. It now probes for a live review-pr first (same probe pair as the scan gate, fail-open on probe errors) and defers the refresh with the retry branch's sentinel semantics instead of superseding the review; the deferred round joins the consecutive-failure exemptions. Rationale recorded as qwen-autofix.md#af-148, and the af-099 delay-window entry is updated for the new supersede semantics. Both workflows' .size-baseline entries are bumped in this PR per the growth ratchet: the review workflow gains the watcher/salvage machinery (+15 KB) and the autofix report step gains the probe pair (+6 KB) — real feature growth, with the long-form rationale kept in the design record. Fixes #10110






What this PR does
The autofix fleet scan now holds a managed PR's whole round whenever an automatic review of that PR's current head is still in flight: it skips both the stale-base branch update and the fix dispatch for that PR until the review lands, and the next scan picks the PR back up. Because the review's own findings are exactly the feedback the next round should batch with, and the skip does not advance the feedback watermark, nothing is dropped by waiting. When a human review is what triggered the held scan, the bot posts one acknowledgment comment per in-flight review run so the human sees their feedback was received and is queued, instead of watching the bot read their review and then go silent. The review workflow itself is untouched.
Why it's needed
On bot-authored PRs the autofix loop and the automatic review cancel each other. Every head mutation the scan makes — a fix push, or a merge-main via branch update — is a synchronize event, and the review workflow cancels its in-progress run on synchronize. The scan was deliberately allowed to act while a review runs (the review check is exempted from the pending-checks gate so its duration does not hide the PR), so on an active PR the two interleave: a review starts, a scan pushes mid-flight, the review dies with up to hours of work discarded, a new review starts, and the cycle repeats. PR #8830 showed the pattern in full: seven commits in ~9.5 hours, the review cancelled and restarted at least six times, two of the kills caused by merge-main pushes alone, and a concurrency cost ledger of 83 API calls and ~8.7M input tokens for one two-file PR. This change removes the interleaving at its source: while a review is in flight on a head, the scan does not mutate that head. The gate also covers the review workflow's 10-minute delay window, where the review run already exists (queued/waiting/pending) but its check does not appear in the status rollup yet, by falling back to a runs-API lookup by head SHA.
Reviewer Test Plan
How to verify
npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js. The new case replays the extracted liveness filter over rollup fixtures (live review-pr in every pending-ish status blocks; a concluded review does not; other checks do not), pins the gate's position ahead of the stale-base update and the feedback dispatch, and pins the ack guards (human-only sender, per-review-run marker, empty-startedAt skip). The existing non-blocking-check test still passes unchanged, confirming the perf(autofix): stop the feedback gate waiting on the LLM review check #7416 behavior (a concluded review never re-blocks the scan) is preserved.review-in-flightrows in the fleet scan table instead of cancelled review runs, zero review-pr cancellations attributable to autofix pushes, and at most one ack comment per review run when a human review arrives mid-flight.Evidence (Before & After)
N/A (CI workflow change; behavior is observable in fleet scan logs and review-run history after merge — see PR #8830 run history for the before-state).
Tested on
Environment (optional)
Unit/workflow tests only (
vitest), plus YAML parse, extracted-scriptbash -n, actionlint (structure; full shellcheck deferred to CI), and jq replays of the new filters against synthetic rollups.Risk & Scope
Linked Issues
Fixes #8888
中文说明
这个 PR 做了什么
Autofix 舰队扫描现在会在某个托管 PR 的当前 head 上仍有自动 review 在运行时,暂缓该 PR 的整轮处理:既不做 stale-base 分支更新,也不派发修复,等 review 落地后由下一次扫描接手。由于 review 自身产出的 findings 正是下一轮应当一并处理的反馈,且跳过不会推进反馈水位线,等待不会丢任何东西。当暂缓这次扫描的触发源是人类 review 时,bot 会按"每个在飞的 review run 一条"的频率发确认评论,让人类看到反馈已收到、已排队,而不是看到 bot 读了 review 之后毫无动静。review workflow 本身一行未动。
为什么需要
在 bot 创建的 PR 上,autofix 循环和自动 review 会互相取消。扫描对 head 的每一次变更——修复 push、或 update-branch 式的 merge main——都是一个 synchronize 事件,而 review workflow 在 synchronize 上取消正在运行的 review。扫描此前被刻意允许在 review 运行期间行动(review 检查被豁免出 pending-checks 门控,避免它的时长把 PR 藏起来),于是在活跃 PR 上两者互相穿插:review 启动 → 扫描中途 push → review 带着几小时的工作被杀 → 新 review 启动 → 循环往复。PR #8830 完整展示了这个模式:约 9.5 小时内 7 个 commit,review 被取消重启至少 6 次,其中两次是 merge-main push 单独造成的,一个两文件 PR 的 concurrency 账本高达 83 次 API 调用、约 870 万输入 token。本改动从源头消除穿插:head 上有 review 在飞时,扫描不变更该 head。门控还覆盖了 review workflow 的 10 分钟延迟窗口——此时 review run 已存在(queued/waiting/pending)但其 check 尚未出现在状态 rollup 中——通过按 head SHA 查 runs API 回退兜底。
评审验证计划
如何验证
npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-workflow.test.js。新用例会在 rollup 夹具上回放提取出的存活过滤器(各种 pending 状态的在飞 review-pr 都阻塞;已完成的 review 不阻塞;其他检查不阻塞),钉住门控位于 stale-base 更新与反馈派发之前的位置,并钉住 ack 的守卫(仅限人类发送者、按 review run 去重的 marker、空 startedAt 跳过)。既有的 non-blocking-check 测试原样通过,确认 perf(autofix): stop the feedback gate waiting on the LLM review check #7416 的行为(已完成的 review 不会重新阻塞扫描)被保留。review-in-flight行而不是被取消的 review run,autofix push 造成的 review-pr 取消归零,人类 review 在 review 运行中到达时每个 review run 至多一条确认评论。前后证据
N/A(CI workflow 改动;行为在合入后可通过舰队扫描日志与 review run 历史观察——before 状态见 PR #8830 的运行历史)。
测试环境
环境(可选)
仅单元/workflow 测试(
vitest),另有 YAML 解析、提取脚本的bash -n、actionlint(结构检查;完整 shellcheck 交给 CI)以及新过滤器在合成 rollup 上的 jq 回放。风险与范围
关联 Issue
Fixes #8888