fix(autofix): answer round-cap refusals on the PR instead of only in logs - #8067
Conversation
|
Thanks for the PR! Template: headings differ from the template ( Problem: clearly observed, not theoretical. The #7836 timeline is specific — cap hit at 06:56 with zero PR notice, shepherd dispatch at 17:43, silent refusal in the Actions log, and a frozen conflict for 4+ hours because the shepherd's per-head-SHA dedup can never re-trigger on a capped PR. Three stacked silences, well documented. Direction: right fix in the right place. The scan owns the round computation, so answering refusals there (rather than duplicating the logic into the shepherd) avoids drift. CHANGELOG has no direct reference, but the autofix fleet infrastructure is actively maintained and this directly improves its observability. Size: not applicable — no core module paths touched. Production changes are 69+/32- in the workflow YAML and 77+/2- in the workflow test script. Approach: the scope feels right. Two fixes sharing one code path and root cause — forced-dispatch refusals answering on the PR, and the cap pause notice covering all managed PRs (not just takeover). Both are needed to close the frozen-conflict chain. The no-dedup choice for forced refusals is well-reasoned (shepherd sends at most one dispatch per head; a human asking twice deserves two answers). No unrelated changes. The commits since the last pass added a verbatim bash replay of the consent gate to the tests — a strengthening, no scope change. Risk: no elevated risk signals — no high-risk paths matched (workflow YAML + test script, none of the revert-correlated runtime paths). Moving on to code review. 🔍 中文说明感谢贡献! 模板:标题与模板不同( 问题:明确观测到的,非理论性。#7836 时间线具体——06:56 达到上限但 PR 零通知,17:43 shepherd 触发调度,Actions 日志中静默拒绝,因 shepherd 按 head SHA 去重、封顶 PR 无新推送导致冲突冻结 4+ 小时。三层静默叠加,记录详尽。 方向:正确的修复放在正确的位置。scan 拥有轮次计算,因此在 scan 侧回答拒绝(而非复制到 shepherd)避免漂移。 规模:不适用——未触及核心模块路径。生产改动:工作流 YAML 69+/32-,工作流测试脚本 77+/2-。 方案:范围合理。两个修复共享同一代码路径和根因——forced-dispatch 拒绝在 PR 上回答,cap 暂停通知覆盖所有受管 PR。两者都是关闭冻结链所必需的。forced 拒绝不去重的选择有充分理由。无无关改动。上次审查后的提交为测试新增了 consent 门控的逐字 bash 重放——是加强,非范围变更。 风险:无升级风险信号——未命中高风险路径(工作流 YAML + 测试脚本,均非与回滚相关的运行时路径)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
Code ReviewIndependent proposal: given the problem (cap refusals invisible on the PR, cap pause notice gated to takeover-only), I would (1) add a forced-dispatch refusal comment in the scan's round-cap gate when Comparison: the PR does exactly this — no simpler path missed. Findings: no critical blockers, no convention violations.
TestingThis is a CI workflow change with no user-visible CLI surface, so real-scenario tmux is N/A. The change is substantiated by the PR's own CI below — the ubuntu CI is settled and green on the reviewed commit:
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The macOS/Windows tests and integration tests are skipped by the workflow's path filters (only 中文说明代码审查独立方案: 给定问题(cap 拒绝在 PR 上不可见、cap 暂停通知仅限接管 PR),我会 (1) 在 scan 的轮次上限门中当 对比: PR 完全按此方案实现——无更简路径遗漏。 发现:无关键阻塞项,无规范违反。
测试CI 工作流改动,无用户可见的 CLI 界面,故真实场景 tmux 不适用。改动由下方 PR 自身 CI 证实——ubuntu CI:在受审提交上已稳定且全绿。macOS/Windows 测试和集成测试被工作流路径过滤器跳过(仅改动 — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 5/5 — clean across every stage; would merge without hesitation. The problem is real and well-documented from #7836 — three stacked silences (cap notice gated to takeover-only, refusal visible only in the Actions log, shepherd dedup freezing conflict handling) that produced a state looking healthy from every dashboard while the PR sat broken for hours. The fix is minimal and correctly placed: both changes live in the scan where the round computation already exists, avoiding logic duplication into the shepherd. The forced-dispatch refusal answers every explicit dispatch (no dedup, well-reasoned), and the cap pause notice now covers all managed PRs with appropriate variant wording. Code follows existing patterns throughout — PAT identity checks, DRY-RUN handling, bilingual bodies with dedup markers, printf-based body construction. The commits since the last pass strengthened the tests: the consent gate is now extracted verbatim and replayed under bash across all four label/takeover permutations, so the core behavioral change is pinned by execution, not just substring assertions. CI is green and settled on this commit. No reservations. Approving, pinned to the reviewed commit. 中文说明置信度:5/5 —— 各阶段均干净,毫不犹豫即可合入。 问题真实且记录详尽(来自 #7836)——三层静默叠加(cap 通知仅限接管、拒绝仅 Actions 日志可见、shepherd 去重冻结冲突处理)产生一个从任何面板看都"健康"的死局。修复最小且位置正确:两处改动都在 scan 侧(轮次计算已在此处),避免向 shepherd 复制逻辑。强制调度拒绝回答每次显式调度(不去重,理由充分),cap 暂停通知现覆盖所有受管 PR 并使用恰当的变体措辞。代码完全遵循现有模式。上次审查后的提交加强了测试:consent 门控现被逐字提取并在 bash 下对四种排列重放,核心行为变更由执行固定,而非仅子串断言。CI 在本提交上已绿色稳定。 无保留意见。批准,固定于受审提交。 — Qwen Code · qwen3.8-max-preview Reviewed at |
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. |
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.
Reviewed — no blockers. Suggestions are inline.
— qwen3.8-max-preview via Qwen Code /review
|
@qwen-code /takeover |
doudouOUC
left a comment
There was a problem hiding this comment.
.github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
.github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.7-max via Qwen Code /review
|
🤝 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. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Round summaryFeedback addressed[rc:3678971670] Suggestion — behavioral replay for the consent gate (implemented) The reviewer noted that the consent gate — the Added a VERBATIM extraction + bash replay of the consent gate under all four label/takeover permutations, consistent with the file's existing behavioral test style:
The extraction is drift-sensitive: if the workflow's consent gate changes, the regex fails and the test breaks. Reviews (no action required)
Verification
中文说明本轮总结已处理的反馈[rc:3678971670] 建议 — 对同意门控进行行为重放测试(已实现) 审阅者指出,同意门控——即根据实时标签和接管状态决定是否跳过上限通知的 新增了对同意门控的逐字提取 + bash 重放测试,覆盖所有四种标签/接管排列组合,与文件中现有的行为测试风格一致:
该提取对漂移敏感:如果工作流中的同意门控发生变更,正则表达式将失败,测试会中断。 审阅(无需操作)
验证
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/模型 |
…er permutations (#8067)
|
@qwen-code /resolve |
Observed on #7836: the fleet shepherd detected a merge conflict, posted 'dispatched the autofix loop to resolve it', and the dispatch died at the scan's round-cap gate with only a log line — the PR page showed a promise, the run showed green, and the conflict sat unhandled for hours. Three silences stacked: the standard-management cap itself is silent (the pause notice was takeover-only, so #7836 hit 10/10 with zero PR-visible notice), the forced-dispatch refusal is silent, and the shepherd dedups per head SHA — a capped PR gets no pushes, so its head never changes and conflict handling froze permanently. Two scan-side changes (the shepherd stays untouched — the windowed round computation lives in the scan and duplicating it would drift): - A FORCED dispatch (shepherd conflict lever or a human) refused at the cap gate now answers on the PR: cap value, what stays unhandled, and the two recovery commands (/retry for a fresh window, /takeover for the raised cap). No dedup — the shepherd sends at most one dispatch per head, and a human asking twice deserves two answers. - The cap pause notice covers ALL managed PRs: the takeover variant keeps its wording, standard bot PRs get their own (/retry or /takeover). Same marker, same once-per-window dedup, same consent and PAT-identity checks — skip wins everywhere, and only the takeover variant requires the label to still be present. After a re-arm the next scheduled scan picks the PR up normally (conflict targets are label-independent), so the frozen-head loop resolves without any shepherd change.
…er permutations (#8067)
2ea035e to
683a074
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
|
Qwen Code did not run conflict resolution for this request. PR #8067 does not currently have merge conflicts with main. |
Local verification — I ran this PR's own bytes against the incident it describesI did not re-read the diff and reason about it; I extracted the round-cap region verbatim out of Two input sources: 16 hand-built scenarios, and a replay of #7836's real comment thread ( 16 scenarios × 2 variants — all 16 behaved exactly as specified
Replay of the real threadThe window/round math is the workflow's, the markers are the ones the bot actually wrote ( (exact bytes captured from Two claims I checked separately, because getting them wrong would make the notices useless
Mutation test: I broke the PR eight waysThe harness kills 8/8. One finding worth a follow-up (non-blocking)The refusal's "no dedup" rationale assumes a narrower trigger set than The justification is "the shepherd sends at most one dispatch per head, and a human asking twice deserves two answers". But Measured on the incident: #7836 took 6 trusted review submissions between the cap (07-28 06:56:36Z) and your Either fix keeps the #7836 chain closed:
Minor
VerdictLGTM — merge. The mechanism is correct on the data that produced the incident, the takeover path is byte-identical, and the one finding is about comment volume, not correctness. I'd take the 中文版本地验证 —— 用这个 PR 自己的字节去跑它所描述的那次事故我没有只读 diff 推理:把 两类输入:16 个手工场景,以及对 #7836 真实评论线程( 16 场景 × 2 变体 —— 16/16 行为与规格一致
真实线程重放窗口/轮次计算用的是 workflow 自己的代码,标记是 bot 当时真写下的(06:56:36Z 的 另外单独核了两条(错了会让通知失效)
变异测试:把这个 PR 破坏了八种harness 杀死 8/8; 一条值得后续处理的发现(不阻塞)拒绝分支"不去重"的理由,假设的触发集比 理由是"shepherd 每个 head 至多一次、人工问两次就答两次"。但 按事故实测:#7836 在封顶(07-28 06:56:36Z)到你的 两种改法都能保住 #7836 这条链:
次要
结论LGTM,可以合。 机制在产生该事故的真实数据上是正确的,接管路径逐字节未变,唯一的发现关乎评论噪音而非正确性; |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
FORCED_PR is populated for every trusted pull_request_review (route emits pr_number for those), not just workflow_dispatch, so on a capped PR each review submission landed in the un-deduped refusal branch — 7 "Dispatch refused" comments on #7836 where 2 carried the information. Answer only workflow_dispatch (the shepherd lever or a human); review submissions stay covered by the once-per-window pause notice. Adds a verbatim behavioral replay of the guard so a dropped EVENT_NAME condition fails the test.
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Autofix review round — PR #8067The round carried a single issue-level review from @wenshao with verdict LGTM — merge, no failed checks, and no inline threads. One non-blocking finding was actionable in scope; the rest were informational. No base merge was performed ( Feedback triage1. The refusal's "no dedup" rationale assumes a narrower trigger set than The finding is correct and was verified against the code: Adopted the reviewer's preferred option (the
2. Minor — a forced dispatch arriving before the window's pause notice posts both comments in one run (S3). — No action. Informational and inherent to the ordering; the two notices serve different purposes (one answers an explicit dispatch, the other is the once-per-window cap reminder). The reviewer rated it harmless. The 3. Minor — test count is 104 → 104 but the description says 103. — No code change. This is a PR-description wording nit, not code. This round's coverage also rides inside an existing 4. Minor — Pre-existing on the base branch and unrelated to this PR (reviewer-confirmed). Not touched. Conflict notesNone. VerificationCommands actually run this round (all from the repository root):
No settings source changed (no 中文说明Autofix 审查轮次 —— PR #8067本轮只有一条来自 @wenshao 的 issue 级审查,结论为 LGTM —— 可合,无失败检查,也没有行内讨论线程。其中一条不阻塞的发现可在本次范围内处理;其余均为信息性。未做 base 合并( 反馈分类1. 拒绝分支"不去重"的理由,假设的触发集比 该发现正确,并已对照代码核实: 采用了审查者更倾向的方案(
2. 次要 —— forced dispatch 若早于本窗口的暂停通知到达,会在一次运行里同时发两条(S3)。—— 不处理。 信息性,且是顺序的自然结果;两条通知用途不同(一条回答一次显式调度,另一条是窗口内一次的封顶提醒)。审查者评定为无害。上面的 3. 次要 —— 用例数为 104 → 104,但描述里写的是 103。—— 不改代码。 这是 PR 描述的措辞瑕疵,非代码问题。本轮覆盖同样写在已有的 4. 次要 —— 审查者机器上 在 base 分支上即已存在,与本 PR 无关(审查者已确认)。不予触碰。 冲突说明无。 验证本轮实际运行的命令(均在仓库根目录):
未改动任何 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/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
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.
中文说明
未发现问题。LGTM!✅
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-autofix.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max-preview via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed at 7d262e1. The two notices split cleanly by trigger and each has the right anti-spam story: the loud Dispatch refused comment fires only under FORCED_PR == PR && EVENT_NAME == 'workflow_dispatch' — the workflow comment documents why (route also sets pr_number for every trusted review submission, which spammed 7 refusals on #7836 before this gate), and dispatch-side dedup is deliberately delegated to the shepherd's per-head-SHA dedup, so a capped PR cannot accumulate refusals from the schedule. The pause notice now covers ALL managed PRs (the takeover-only gate was the root of #7836's silent 10/10 cap) with once-per-window dedup keyed on REARM_KEY; I verified the 'none' → '' fallback specifically — without it, created_at > "none" is lexically always false and the notice would repost every scan, and the code handles exactly that. Consent is re-checked live before posting (skip label wins everywhere; a takeover notice requires the label still present), and the PAT-identity check prevents the rotated-PAT repost loop the dedup could never see. Both bodies are built from constants and integer counters only — no PR-controlled content, no injection surface. Markers are distinct per notice type (takeover-cap-refused vs takeover-cap-reached, the latter shared intentionally across takeover/standard wording with the split count pinned at 3). Scope claim matches the code: this fixes visibility and hands humans the /retry / /takeover unlock; it does not touch the shepherd's head-SHA dedup itself. Tests replay the extracted refusal guard and the dedup jq against fixtures (both event names, old-window/in-window/no-key), not just string-pins. Ran the changed test file locally at this HEAD: 104/104 passed. CI green. No findings.
|
Released in v0.21.2. |




Problem
Observed end-to-end on #7836 (2026-07-29):
qwen-autofixwithpr_number=7836.🚧 #7836: hit the round cap (10/10)— visible only in the Actions log. The run completed green.Three silences stacked into a frozen state that looks healthy from every dashboard.
Change
Both fixes live in the scan — the shepherd stays untouched, because the windowed round computation (
REARM_KEY,win=scoping, effective cap) has exactly one correct implementation and duplicating it into the shepherd would drift.1. Forced-dispatch refusals answer on the PR. When the refused candidate is the run's
FORCED_PR(shepherd conflict lever or a human dispatch), the cap gate posts: the cap value, what stays unhandled (the conflict/feedback that triggered the dispatch), and the two recovery commands —@qwen-code /retry(fresh window, same cap) or@qwen-code /takeover(raised cap). No dedup by design: the shepherd sends at most one dispatch per head, and a human asking twice deserves two answers. The PR page now reads "🐑 dispatched" → "⏸️ Dispatch refused: … /retry to re-arm" instead of a promise followed by silence.2. The cap pause notice covers all managed PRs. The takeover variant keeps its existing wording; standard bot PRs get their own ("⏸️ AutoFix paused …
/retry… or/takeover"). Same<!-- takeover-cap-reached -->marker → same once-per-window dedup, same consent re-check (skip wins everywhere; only the takeover variant requires the label to still be present) and the same PAT-identity convention.After a re-arm, the next scheduled scan picks the PR up through the normal path (conflict targets are label-independent), so the frozen-head loop resolves without touching the shepherd's dedup.
Tests
takeover-cap-reachednow appears at 3 sites (dedup read + both bodies) — pinned; the ack-body bilingual census grows 12 → 14 (standard pause + refusal), each individually carrying collapsed Chinese.FORCED_PR == PRguard, the refusal marker/wording, DRY-RUN and PAT-identity lines for the refusal path, and both/retry-bearing bodies as printf args.qwen-autofix-workflow.test.js(+ 14/14 package-scripts); workflow YAML parses.中文说明
问题
在 #7836 上完整观测到的链路:bot 自建 PR 于 07-28 06:56 跑满常规上限(10/10),PR 页面零通知(暂停通知此前仅限接管 PR);随后 main 前进产生冲突,fleet shepherd 17:43 发"🐑 已触发 autofix 处理"并 dispatch;扫描在轮次上限门静默拒绝(仅 Actions 日志可见),运行绿色收场;shepherd 按 head SHA 去重、而封顶 PR 不会有新推送,head 永不变化——冲突处理被永久冻结,页面上却挂着一句承诺。三层静默叠加成一个从任何面板看都"健康"的死局。
改动
两处都在 scan 侧(shepherd 一行不动——窗口化轮次计算只有 scan 里一份正确实现,复制即漂移):
/retry重置窗口 //takeover升上限)。不去重(shepherd 每 head 至多一次 dispatch;人工问两次就答两次)。页面链路变为"🐑 已触发 → ⏸️ 已拒绝 + 怎么办"。/retry或/takeover)。同一标记 → 同一 once-per-window 去重,同样的 consent 复查(skip 全局优先;仅接管变体要求标签仍在)与 PAT 身份校验。re-arm 之后由下一次定时扫描按常规路径接手(冲突目标的选取与标签无关),冻结自然解除。
测试
标记三处计数 pin;双语正文普查 12 → 14(新增两条均带折叠中文);FORCED 守卫、拒绝文案、DRY-RUN 与 PAT 身份行、两处
/retryprintf 参数均有 pin。103/103 + 14/14 通过;YAML 解析正常。