Skip to content

fix(ci): rename triage status marker to avoid duplicate-guard collision - #7723

Merged
wenshao merged 11 commits into
QwenLM:mainfrom
yiliang114:fix/triage-lifecycle-marker
Jul 26, 2026
Merged

fix(ci): rename triage status marker to avoid duplicate-guard collision#7723
wenshao merged 11 commits into
QwenLM:mainfrom
yiliang114:fix/triage-lifecycle-marker

Conversation

@yiliang114

@yiliang114 yiliang114 commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Moves the triage lifecycle status comment out of the reserved stage= marker namespace by using <!-- qwen-triage lifecycle -->. The new marker keeps compatibility with existing autofix comment filters, and legacy stage=status comments are reused and rewritten instead of leaving affected threads poisoned or creating duplicate status comments.

Why it is needed

The duplicate guard is intended for completed numbered triage stages. The lifecycle status comment previously used stage=status, so it could be mistaken for completed analysis and cause an unattended triage run to exit without posting results.

Reviewer Test Plan

How to verify

  1. On a thread with a legacy lifecycle status comment, start triage and confirm the existing comment is updated to the new marker before analysis runs.
  2. Confirm the start, completion, and deferred-finalize paths all find the same lifecycle comment.
  3. Run npx vitest run scripts/tests/qwen-triage-workflow.test.js scripts/tests/qwen-triage-finalize-workflow.test.js; all 34 tests should pass.

Evidence (Before & After)

Before: lifecycle status used the reserved stage= namespace and could trip the duplicate guard. After: lifecycle status uses a non-stage marker while legacy comments are migrated in place.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows N/A
🐧 Linux ⚠️ CI pending

Environment (optional)

Local Node 22 workspace.

Risk & Scope

  • Main risk or tradeoff: marker migration touches comment lookup in the main and deferred-finalize paths.
  • Not validated / out of scope: other triage stage markers and review decision logic.
  • Breaking changes / migration notes: existing stage=status comments are migrated automatically on their next update.

Linked Issues

Observed in #7713.

中文说明

本 PR 做了什么

把 triage 生命周期状态评论移出保留的 stage= marker 命名空间,改用 <!-- qwen-triage lifecycle -->。新 marker 继续兼容现有 autofix 评论过滤器;旧的 stage=status 评论会被复用并改写,不会继续污染已受影响线程或产生重复状态评论。

为什么需要

Duplicate guard 只应识别已经完成的数字阶段。生命周期状态评论之前使用 stage=status,可能被误认为已经完成分析,导致无人值守的 triage 运行提前退出且不发布结果。

Reviewer Test Plan

如何验证

  1. 在存在旧生命周期状态评论的线程启动 triage,确认分析开始前旧评论被原地更新为新 marker。
  2. 确认开始、结束和 deferred-finalize 路径都能找到同一条生命周期评论。
  3. 运行上面的两个聚焦测试文件,34 个测试应全部通过。

前后证据

Before:生命周期状态使用保留的 stage= 命名空间,可能触发 duplicate guard。After:生命周期状态使用非 stage marker,并原地迁移旧评论。

测试环境

macOS 已验证;Windows 不适用;Linux CI 运行中。

风险与范围

  • 主要风险:marker 迁移会修改主流程和 deferred-finalize 的评论查找。
  • 未验证 / 不在范围:其他 triage stage marker 与 review 决策逻辑。
  • 破坏性变更 / 迁移说明:现有 stage=status 评论会在下次更新时自动迁移。

#7713 中观察到。

The lifecycle status comment used <!-- qwen-triage stage=status --> which
the triage agent's duplicate guard sometimes matches as a prior stage
marker (stage=N), causing it to exit without posting the actual triage
analysis. Rename to <!-- qwen-triage-lifecycle --> so the guard never
matches infrastructure comments.

Fixes the probabilistic silent-triage gap seen in QwenLM#7713.
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🔄 Qwen Triage is runningwatch live progress. Stage results will post in this thread as they complete.

🔄 Qwen Triage 正在运行 —— 查看实时进度。各阶段结果完成后会更新在本线程。

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR — and for iterating through the review feedback so thoroughly.

Template looks good ✓

Problem: observed bug with evidence — #7713 silently skipped triage because the lifecycle status comment carried stage=status, which the duplicate guard matched as a prior stage result. Real issue, clearly reproduced.

Direction: aligned — this fixes the triage infrastructure's own marker collision. No CHANGELOG signal needed for internal CI tooling.

Size: not applicable (CI workflow files, not core module paths).

Approach: the <!-- qwen-triage lifecycle --> marker is a clean solution — it drops the stage= prefix so the duplicate guard can never match it, while preserving the <!-- qwen-triage prefix that the autofix bot-comment filter depends on. The legacy migration, author filtering, and startswith tightening all address prior review feedback directly. Scope is tight, no drive-by changes.

Moving on to code review. 🔍

中文说明

感谢贡献——也感谢你如此彻底地迭代了审阅反馈。

模板完整 ✓

问题:已观测到的 bug,有证据——#7713 因生命周期状态评论携带 stage=status 而被 duplicate guard 误认为已有阶段结果,导致 triage 静默跳过。真实问题,复现清晰。

方向:对齐——修复 triage 基础设施自身的 marker 碰撞。内部 CI 工具无需 CHANGELOG 信号。

规模:不适用(CI workflow 文件,非核心模块路径)。

方案:<!-- qwen-triage lifecycle --> marker 是一个干净的方案——移除 stage= 前缀使 duplicate guard 永远不会匹配,同时保留 autofix bot 评论过滤器所依赖的 <!-- qwen-triage 前缀。Legacy 迁移、作者过滤和 startswith 收紧都直接回应了此前的审阅反馈。范围紧凑,无夹带改动。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

Reviewed at 7bb679415f1d3b7ca6eecba008bbc53e62b87927 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Code review

Independent proposal: rename the lifecycle marker out of the stage= namespace, keep the <!-- qwen-triage prefix for autofix filter compatibility, add a legacy-marker OR in the jq upsert so existing threads self-heal, and filter on bot authorship + startswith to prevent false matches on human comments that quote the marker. That's exactly what this PR does — no simpler path exists.

The implementation is consistent across all three emit sites (post step, finalize step in qwen-triage.yml, and the deferred-finalize in qwen-triage-finalize.yml). Each resolves the bot login first and gracefully skips if it can't — reasonable for a best-effort status comment. The jq filter select(.user.login == $bot) | select((.body | startswith($m)) or (.body | startswith($legacy))) is correct: author-filtered, prefix-matched, and the PATCH body always writes the new marker so legacy comments migrate on first contact.

Tests are load-bearing: they assert the exact marker strings, the exact jq filter pattern, and include negative assertions (not.toContain('contains($m)')) that would catch a regression to the weaker match. The finalize test pins both STATUS_MARKER and LEGACY_STATUS_MARKER literals.

The autofix filter question from earlier rounds is resolved by construction: <!-- qwen-triage lifecycle --> contains <!-- qwen-triage (with trailing space), which the alternation at all four qwen-autofix.yml sites already matches. No changes needed there.

Remaining nit (non-blocking, acknowledged in the PR): stage=rerun-summary at qwen-triage.yml:789 still occupies the reserved namespace. Narrow reachable path, reasonable to defer to a follow-up.

CI evidence

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ pass (33m34s)
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ pass (6m16s)
Classify PR ✅ pass
precheck-pr / precheck ✅ pass
review-pr ✅ pass (21m51s)
remove-suspicious-attachments ✅ pass
route ✅ pass
Remind on force-push ✅ pass
label ✅ pass

All 11 non-skipped checks green. macOS/Windows/integration tests skipped (fork PR, expected).

Real-scenario testing: N/A — CI workflow internals, nothing user-visible.

中文说明

代码审查

独立方案:将生命周期 marker 移出 stage= 命名空间,保留 <!-- qwen-triage 前缀以兼容 autofix 过滤器,在 jq upsert 中加入旧 marker 的 OR 条件使已有线程自愈,并按 bot 作者 + startswith 过滤以防止匹配引用了 marker 的人类评论。PR 正是这样做的——没有更简路径。

实现在全部三处发布点(post 步骤、qwen-triage.yml 的 finalize 步骤、以及 qwen-triage-finalize.yml 的 deferred-finalize)保持一致。每处都先解析 bot 登录名,无法解析时优雅跳过——对于尽力而为的状态评论来说合理。jq 过滤器正确:按作者过滤、前缀匹配,且 PATCH 正文始终写入新 marker,旧评论在首次接触时即完成迁移。

测试是有效的:断言了精确的 marker 字符串、精确的 jq 过滤器模式,并包含负向断言以捕获回退到弱匹配的回归。finalize 测试固定了 STATUS_MARKERLEGACY_STATUS_MARKER 字面量。

此前各轮提出的 autofix 过滤器问题已从构造上解决:新 marker 包含 <!-- qwen-triage (带尾部空格),qwen-autofix.yml 四处候选分支已能匹配。无需修改。

剩余细节(非阻塞,PR 中已说明):qwen-triage.yml:789stage=rerun-summary 仍占用保留命名空间。可达路径较窄,合理推迟到后续 PR。

CI 证据

全部 11 个非跳过检查项通过。macOS/Windows/集成测试跳过(fork PR,预期行为)。

真实场景测试: N/A——CI workflow 内部改动,无用户可见变化。

Qwen Code · qwen3.8-max-preview

Reviewed at 7bb679415f1d3b7ca6eecba008bbc53e62b87927 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

Comment thread .github/workflows/qwen-triage.yml Outdated
Comment thread .github/workflows/qwen-triage.yml Outdated

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] qwen-triage-finalize.yml:95 still defines STATUS_MARKER='' (existing blocker from previous review — still stands). The finalize workflow's comment lookup will silently fail after this rename.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

中文说明

已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。

— qwen3.7-max via Qwen Code /review

Comment thread .github/workflows/qwen-triage-finalize.yml Outdated
@gwinthis

Copy link
Copy Markdown
Collaborator

Review + verification report (Linux, tests + marker-shape audit)

Verdict: the rename is correct, complete for the marker it targets, and safe for every downstream consumer I could find — but the same collision shape survives in one more marker (stage=rerun-summary), which re-creates this exact bug on re-runs. Recommend renaming it in the same PR.

Verification evidence (commit 74fb91d)

  • Rename is exhaustive for stage=status: repo-wide grep finds zero remaining occurrences; both post and finalize steps in qwen-triage.yml and the author-verified upsert in qwen-triage-finalize.yml use the new <!-- qwen-triage-lifecycle -->.
  • Collision eliminated by construction, not by model behavior: the duplicate guard keys on the substring <!-- qwen-triage stage= (per .qwen/skills/triage/SKILL.md: "The guard matches against this marker"). The new marker no longer contains stage=, so even the loosest model interpretation cannot read it as a completed stage. Shape simulation: old marker matches the guard pattern, qwen-triage-lifecycle does not.
  • Downstream consumers stay correct: the /review cleanup bypass-audit filters automation comments by the prefix '<!-- qwen-' (cleanup.ts:54), so the renamed lifecycle comment is still excluded from bypass warnings — no new false positives introduced.
  • Tests: qwen-triage-workflow.test.js 15/15 passed on this branch.
  • Risk note in the PR (pre-rename lifecycle comments get a new comment instead of an upsert on the next run) is accurate and acceptable for ephemeral status comments.

Finding: stage=rerun-summary has the identical collision shape

qwen-triage.yml:619 still posts:

<!-- qwen-triage stage=rerun-summary -->

That substring matches the duplicate-guard pattern exactly the way stage=status did. It's posted after the agent step, so a first run is safe — but on the next re-run of the same issue (precisely the scenario in this PR's own test plan step 2), the guard scans the thread, sees stage=rerun-summary from the previous run, and can misread it as a prior triage stage — the same probabilistic silent-skip this PR fixes, just moved to re-runs. Suggest renaming it to a non-stage= form (e.g. <!-- qwen-triage-rerun-summary -->) in this PR while the context is warm; it has no upsert logic keyed to the old string, so the rename is a one-liner plus the test pin.

@gwinthis

Copy link
Copy Markdown
Collaborator

Round-2 status check (head bf197af)

Verified the follow-up commit (test(ci): cover triage status marker parity) on Linux:

  • qwen-triage-workflow.test.js: 15/15 passed — the parity pins on both post and finalize steps hold.
  • qwen-triage-finalize-workflow.test.js: 18/20 passed; the 2 failures are purely environmental (jq: command not found on this host — the tests replay embedded jq pipelines that CI has available). Not related to this PR.

The round-1 finding stands at this head: qwen-triage.yml:619 still posts <!-- qwen-triage stage=rerun-summary -->, which matches the duplicate-guard shape (<!-- qwen-triage stage=) and can re-create the silent-skip on the next re-run of the same issue. Still recommend renaming it to a non-stage= form in this PR — one line plus a test pin, and the new parity test file is the natural home for it.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] .github/workflows/qwen-autofix.yml:2292 — The rename from <!-- qwen-triage stage=status --> to <!-- qwen-triage-lifecycle --> breaks the autofix workflow's BOT_COMMENT_FILTER regex. The regex qwen-triage followed by a literal space matched the old marker (space before stage), but the new marker has a hyphen (qwen-triage-) instead. The lifecycle comment now passes through the autofix filter and is counted as actionable feedback, potentially triggering unnecessary autofix re-evaluation cycles. Four regex sites affected: lines 2292, 2733, 2818, 2895. Failure scenario: autofix runs on a PR with a triage lifecycle comment → filter doesn't match → comment counted as user feedback → unnecessary agent turn.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Review: rename triage status marker

Overview

Renames the lifecycle status marker from <!-- qwen-triage stage=status --> to <!-- qwen-triage-lifecycle --> at all three emit sites (qwen-triage.yml:314, :640, qwen-triage-finalize.yml:95), updates two test files, and adds a cross-workflow parity test.

The premise checks out. "Post triage status comment" (qwen-triage.yml:305) runs before "Run Qwen Triage" (:359), so on every unattended run the agent sees a stage=-marked bot comment in the thread before it starts — the collision isn't probabilistic in presence, only in interpretation. Live evidence matches the report exactly: #7713 carries only <!-- qwen-triage stage=status --> and zero stage comments, while #7717 and #7719 carry stage=status and stage=1. The rename itself is complete (no stage=status remains anywhere at head) and the new name is consistent with the repo's other infrastructure markers — qwen-triage-ci, qwen-triage approve-on-green, qwen-triage:tmux — which already avoid the stage= namespace.


🔴 Blocker — the rename breaks the autofix bot-comment filter (4 sites)

.github/workflows/qwen-autofix.yml:2292, 2733, 2818, 2895 filter non-actionable bot comments with an alternation followed by a literal trailing space:

<!-- (autofix-eval|autofix-rearm|autofix-base-updated|qwen-triage|qwen-review-suggestion-summary|pr-force-push|qwen-review-ack) 

<!-- qwen-triage stage=status --> matched (qwen-triage + space). <!-- qwen-triage-lifecycle --> has a hyphen in that position, so it does not. Verified with jq against the exact filter string and the exact comment bodies:

OLD marker      -> true    (filtered as bot noise)
NEW marker      -> false   (NOT filtered)  ← regression
stage=1 comment -> true    (still filtered)

That regex is the only defense at these sites. The lifecycle comment is authored by qwen-code-ci-bot, which is REVIEW_BOT (:103) and not AUTOFIX_BOT (:97), with author_association: COLLABORATORTRUSTED_ASSOC (:107) — so it clears both select(.user.login != $ab) and the trust gate, on two independent grounds.

Failure scenario: the autofix bot opens a PR → triage fires on pull_request_target: opened → the lifecycle comment is created → autofix's next scan counts it in N_ISSUE_COMMENTS → a full agent round is burned "addressing" a 🔄 Qwen Triage is running notice. The same body flows through :2733 (advances the NEWEST watermark) and :2818/:2895 (defeats LIVE_NEW stale-duplicate suppression).

Fix — add to the alternation at all four sites. Keep qwen-triage first so the existing prefix assertion in qwen-autofix-workflow.test.js:5714 stays green (jq's oniguruma backtracks, so either order matches — verified both):

-...|qwen-triage|qwen-review-suggestion-summary|...
+...|qwen-triage|qwen-triage-lifecycle|qwen-review-suggestion-summary|...

Applied locally at all four sites: qwen-autofix-workflow + qwen-triage-workflow + qwen-triage-finalize-workflow = 137 tests, all pass.


🟡 Should fix — legacy comments keep the collision; the fix isn't retroactive

Sampled the 60 most-recently-updated open issues and the 60 most-recently-updated open PRs: 31 of them already carry a <!-- qwen-triage stage=status --> comment. Issues #7712 and #7713 show the exact silent-triage signature (status marker present, zero real stage comments) — precisely the population this PR is meant to rescue.

Post-merge, those threads keep the legacy marker verbatim, so the guard collision persists there, and the upsert no longer finds the old comment — it creates a second lifecycle comment instead of updating. That compounds with the blocker above: on every already-triaged PR, the next triage run creates a brand-new, now-unfiltered lifecycle comment.

A two-line fallback at each upsert (qwen-triage.yml:319-324, :649-654, and qwen-triage-finalize.yml:323) makes this self-healing:

+          LEGACY_MARKER='<!-- qwen-triage stage=status -->'
           EXISTING_ID="$(
             gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
               --method GET --paginate -F per_page=100 |
-              jq -rs --arg m "$MARKER" \
-                '[.[][] | select(.body | contains($m))] | last | .id // empty'
+              jq -rs --arg m "$MARKER" --arg legacy "$LEGACY_MARKER" \
+                '[.[][] | select(.body | contains($m) or contains($legacy))] | last | .id // empty'
           )" || EXISTING_ID=''

The PATCH body always writes $MARKER, so the first re-run rewrites the legacy marker out of existence. This retires the PR's stated migration caveat entirely rather than accepting it.


🟡 Should fix — test-plan step 2 cannot verify the fix

Re-run triage on #7713 via workflow_dispatch to confirm it now produces the stage comment.

This is non-discriminating. SKILL.mdDuplicate Guard: explicit reruns (workflow_dispatch / issue_comment) run all stages — the guard never fires on that path. The step passes identically with or without this rename. The only discriminating check is an unattended event (issues: edited/reopened, or pull_request_target: opened) on a thread whose lifecycle comment already carries the new marker.


🔵 Nit — stage=rerun-summary is still in the reserved namespace

qwen-triage.yml:619 still posts <!-- qwen-triage stage=rerun-summary -->. By the PR's own stated model ("the guard matches any comment containing <!-- qwen-triage stage= followed by content and -->"), this is the same collision, and it's posted by infrastructure rather than by the agent — same as the marker being renamed here.

The reachable path is genuinely narrow, so this isn't a blocker: rerun-summary only posts on issue_comment for PRs, and triage's only unattended PR trigger is pull_request_target: [opened, ready_for_review], so the thread would have to go draft → ready afterwards. But renaming it now, while the namespace convention is being established, closes the class instead of the instance.


🔵 Nit — the new parity test can pass vacuously

Mutation-tested scripts/tests/qwen-triage-finalize-workflow.test.js:148:

Mutation Result
Revert finalize STATUS_MARKER to the old value fails ✅ load-bearing for its stated purpose
Rename the shell var on both sides (MARKER=/STATUS_MARKER=LIFECYCLE_VAR=) passes green ❌ both regexes return undefined; expect(undefined).toBe(undefined)
Drift only the second MARKER= in qwen-triage.yml (:640) passes ❌ the regex reads the first match only

The last one is caught by the sibling assertion in qwen-triage-workflow.test.js:228, so suite coverage holds — but the parity test under-delivers on its name. /MARKER='([^']+)'/ is also unanchored, so it would silently retarget APPROVE_MARKER= / STATUS_MARKER= if either ever moved earlier in the file.

const triageStatusMarker = triageWorkflowText.match(/^\s*MARKER='([^']+)'/m)?.[1];
// pin the literal so a two-sided rename can't turn this into a no-op
expect(triageStatusMarker).toBe('<!-- qwen-triage-lifecycle -->');
expect(finalizeStatusMarker).toBe(triageStatusMarker);

Verdict

Right diagnosis, right shape, and the rename is applied consistently across every emit site. Blocked on the autofix filter regression — the old marker's shape was load-bearing for a consumer in another workflow, and renaming it silently reclassifies the lifecycle comment as actionable reviewer feedback. Worth folding in the legacy-marker fallback too, since without it the fix doesn't reach the 31 open threads (including #7712 / #7713) that still carry the old marker.

中文说明

概览

将生命周期状态标记从 <!-- qwen-triage stage=status --> 重命名为 <!-- qwen-triage-lifecycle -->,覆盖全部三处发布点(qwen-triage.yml:314:640qwen-triage-finalize.yml:95),并更新两个测试文件、新增一个跨 workflow 的一致性测试。

前提成立。 "Post triage status comment"(qwen-triage.yml:305)在 "Run Qwen Triage"(:359)之前执行,因此每次非托管运行时 agent 启动前线程里必然已存在一条带 stage= 标记的 bot 评论——冲突并非「概率性出现」,而只是模型解读上的概率性。线上证据完全吻合:#7713 只有 <!-- qwen-triage stage=status --> 且没有任何 stage 评论,而 #7717#7719 同时有 stage=statusstage=1。重命名本身是彻底的(head 上已无任何 stage=status),新名称也与仓库其他基础设施标记(qwen-triage-ciqwen-triage approve-on-greenqwen-triage:tmux)保持一致——它们本就避开了 stage= 命名空间。

🔴 阻塞项 —— 重命名破坏了 autofix 的 bot 评论过滤(4 处)

.github/workflows/qwen-autofix.yml:2292、2733、2818、2895 用「候选分支 + 一个字面空格」过滤非可执行的 bot 评论。旧标记 <!-- qwen-triage stage=status --> 能匹配(qwen-triage 后跟空格),而 <!-- qwen-triage-lifecycle --> 在该位置是连字符,匹配失败。已用 jq 以真实过滤串和真实评论正文验证:旧标记 → true(被过滤),新标记 → false未被过滤,回归),stage=1 评论 → true

该正则是这些位置唯一的防线:生命周期评论的作者是 qwen-code-ci-bot,即 REVIEW_BOT:103)且不是 AUTOFIX_BOT:97),author_associationCOLLABORATORTRUSTED_ASSOC:107),因此会同时通过登录名过滤和信任门禁两道关卡。

故障场景: autofix bot 开启 PR → triage 在 pull_request_target: opened 触发 → 创建生命周期评论 → autofix 下一次扫描把它计入 N_ISSUE_COMMENTS → 白白消耗一整轮 agent 去「处理」一条 🔄 Qwen Triage is running 通知。同一正文还会流经 :2733(推进 NEWEST 水位线)与 :2818/:2895(破坏 LIVE_NEW 的重复运行抑制)。

修复: 在四处候选分支中加入 qwen-triage-lifecycle,并把 qwen-triage 保留在前,以免破坏 qwen-autofix-workflow.test.js:5714 现有的前缀断言(jq 的 oniguruma 会回溯,两种顺序均可匹配,已双向验证)。本地在四处应用后:qwen-autofix-workflow + qwen-triage-workflow + qwen-triage-finalize-workflow137 个测试全部通过

🟡 建议修复 —— 历史评论仍带冲突;修复不具追溯性

抽样最近更新的 60 个开放 issue 与 60 个开放 PR:其中 31 个已经带有 <!-- qwen-triage stage=status --> 评论。issue #7712#7713 呈现出典型的静默 triage 特征(有 status 标记、零 stage 评论)——恰恰是本 PR 想要挽救的那一类。

合并后,这些线程仍原样保留旧标记,冲突依旧存在;同时 upsert 也找不到旧评论,会新建第二条而非更新。这还会与上面的阻塞项叠加:每个已 triage 过的 PR 在下次运行时都会新建一条「不再被过滤」的生命周期评论。

在三处 upsert(qwen-triage.yml:319-324:649-654,以及 qwen-triage-finalize.yml:323)各加两行回退查找即可自愈:查找条件改为「新标记 旧标记」。由于 PATCH 正文始终写入 $MARKER,第一次重跑就会把旧标记改写掉。这样可以彻底消除 PR 中所述的迁移遗留问题,而不是接受它。

🟡 建议修复 —— 测试计划第 2 步无法验证该修复

「通过 workflow_dispatch 重新运行 #7713 的 triage」不具区分度。SKILL.mdDuplicate Guard 明确:显式重跑(workflow_dispatch / issue_comment会跑完所有 stage——守卫在该路径上根本不会触发。无论是否有本次重命名,这一步都会通过。唯一具区分度的检查是在非托管事件(issues: edited/reopened,或 pull_request_target: opened)下,针对生命周期评论已带新标记的线程进行验证。

🔵 细节 —— stage=rerun-summary 仍在保留命名空间内

qwen-triage.yml:619 仍然发布 <!-- qwen-triage stage=rerun-summary -->。按 PR 自述的模型(「守卫匹配任何包含 <!-- qwen-triage stage= 后跟内容和 --> 的评论」),这是同一类冲突,而且同样由基础设施而非 agent 发布。

可达路径确实较窄,因此不算阻塞:rerun-summary 仅在 PR 的 issue_comment 下发布,而 triage 唯一的非托管 PR 触发器是 pull_request_target: [opened, ready_for_review],需要线程之后经历 draft → ready 的转换。但趁着命名空间约定正在确立的时机一并改名,可以关掉整个类别而不只是这一个实例。

🔵 细节 —— 新增的一致性测试可能空转通过

scripts/tests/qwen-triage-finalize-workflow.test.js:148 做了变异测试:

变异 结果
把 finalize 的 STATUS_MARKER 改回旧值 失败 ✅ 对其既定目的是有效的
两侧同时重命名 shell 变量(MARKER=/STATUS_MARKER=LIFECYCLE_VAR= 通过 ❌ 两个正则都返回 undefinedexpect(undefined).toBe(undefined)
只改动 qwen-triage.yml 中第二处 MARKER=:640 通过 ❌ 正则只读取第一个匹配

最后一项会被 qwen-triage-workflow.test.js:228 的同类断言捕获,所以整体套件覆盖没有漏洞——但这个一致性测试本身没有兑现它的名字。另外 /MARKER='([^']+)'/ 未加锚定,一旦 APPROVE_MARKER= / STATUS_MARKER= 挪到文件更靠前的位置,它就会静默地匹配到错误目标。建议改用 /^\s*MARKER='([^']+)'/m 并直接断言字面量,避免双侧重命名把它变成空操作。

结论

诊断正确、方案方向正确,重命名在所有发布点上应用得也很彻底。阻塞点在于 autofix 过滤回归——旧标记的形状被另一个 workflow 的消费者所依赖,重命名会静默地把生命周期评论重新归类为「可执行的审阅反馈」。同时建议一并加入旧标记回退查找,否则修复无法覆盖仍带旧标记的 31 个开放线程(包括 #7712 / #7713)。

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Re-checked all three review comments — they are all false positives:

  1. "Tests still assert old marker" — Already verified: qwen-triage-workflow.test.js lines 216 and 231 both assert <!-- qwen-triage-lifecycle -->, the new marker.
  2. "finalize.yml line 95 still has old marker" — Already verified: STATUS_MARKER='<!-- qwen-triage-lifecycle -->' at line 95.
  3. "finalize test doesn't pin STATUS_MARKER" — Already has the test: 'uses the same status marker as the triage workflow' which asserts finalizeStatusMarker === triageStatusMarker.

All files are consistent with the new marker. No code changes needed.

@wenshao

wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Re-review at head bf197afb (unchanged since the last round)

First, on the "all three are false positives" replyyou're right about those three. They are the bot's inline comments from 16:01/17:26 UTC, and commits 86fe06cb / 74fb91d5 / bf197afb did fix all of them. Re-confirmed on this head:

Dismissed claim Status at bf197afb
Tests still assert old marker ✅ stale — qwen-triage-workflow.test.js:216,231 assert the new marker
finalize.yml:95 still old ✅ stale — STATUS_MARKER='<!-- qwen-triage-lifecycle -->'
finalize test doesn't pin STATUS_MARKER ✅ stale — parity test added at :148

But the newest top-level review (CHANGES_REQUESTED, 2026-07-25 20:42 UTC — submitted after bf197afb at 19:53) is a different finding and is not in that list. It is still live, and I reproduce it independently below. There is no stage=status left anywhere on this head, so the rename itself is complete and correct — the problem is a consumer in another workflow.


🔴 Blocker — the rename breaks qwen-autofix's bot-comment filter (4 sites)

.github/workflows/qwen-autofix.yml:2292, 2733, 2818, 2895 filter non-actionable bot comments with an alternation followed by a literal trailing space:

<!-- (autofix-eval|autofix-rearm|qwen-triage|qwen-review-suggestion-summary|pr-force-push|qwen-review-ack) 

<!-- qwen-triage stage=status --> matched (qwen-triage + space). <!-- qwen-triage-lifecycle --> has a hyphen in that position and does not. The old marker's shape was a load-bearing contract for this consumer.

Oracle — real filter string, real comment bodies, jq (same engine as the workflow):

$ jq -rn --arg b "$BODY" --arg f "$FILTER" '$b | test($f)'
  OLD  <!-- qwen-triage stage=status -->  → true    (filtered out)
  NEW  <!-- qwen-triage-lifecycle -->     → false   ← REGRESSION
  ctrl <!-- qwen-triage stage=1 -->       → true    (unchanged)

This regex is the only defense here. The lifecycle comment's author is qwen-code-ci-bot = REVIEW_BOT (:103), which is not AUTOFIX_BOT (:97), and its author_association is COLLABORATORTRUSTED_ASSOC (:107). It clears both the login filter and the prompt-injection trust gate; only the marker regex stops it.

Failure scenario: autofix bot opens a PR → triage fires on pull_request_target: opened → posts the lifecycle comment → autofix's next scan counts it in N_ISSUE_COMMENTS → a full agent round is spent "addressing" a 🔄 Qwen Triage is running notification. The same body also flows through :2733 (advances the NEWEST watermark) and :2818/:2895 (breaks LIVE_NEW duplicate-run suppression).

Fix — add the new name to the alternation, keeping qwen-triage first so the existing prefix assertion at qwen-autofix-workflow.test.js:5714 stays intact (oniguruma backtracks, so both orders match):

-<!-- (autofix-eval|autofix-rearm|qwen-triage|qwen-review-suggestion-summary|pr-force-push|qwen-review-ack) 
+<!-- (autofix-eval|autofix-rearm|qwen-triage|qwen-triage-lifecycle|qwen-review-suggestion-summary|pr-force-push|qwen-review-ack) 

Verified locally at all 4 sites: new marker → true, stage=1true, and qwen-autofix-workflow + qwen-triage-workflow + qwen-triage-finalize-workflow = 137/137 passing (identical to the 137/137 baseline on this head).


🟡 Should fix — the fix isn't retroactive; existing threads keep the collision

Sampled the 60 most-recently-updated open issues + 60 open PRs, counting only comments authored by the bot whose body starts with the marker (so quoted markers in review text don't inflate it):

After merge those threads keep the old marker and the collision, and the upsert won't find them — qwen-triage.yml:319-324 posts a second comment, and qwen-triage-finalize.yml:323-326 fails closed ([ -n "$sid" ] || return 0) and silently skips the update, leaving a permanent "🔄 running". This also compounds the blocker: every re-triaged thread gains a fresh unfiltered lifecycle comment.

Two lines per upsert site self-heals it, since the PATCH body always writes $MARKER:

+          LEGACY_MARKER='<!-- qwen-triage stage=status -->'
           EXISTING_ID="$(
             gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
               --method GET --paginate -F per_page=100 |
-              jq -rs --arg m "$MARKER" \
-                '[.[][] | select(.body | contains($m))] | last | .id // empty'
+              jq -rs --arg m "$MARKER" --arg legacy "$LEGACY_MARKER" \
+                '[.[][] | select(.body | contains($m) or contains($legacy))] | last | .id // empty'
           )" || EXISTING_ID=''

🟡 Should fix — test-plan step 2 can't discriminate

Re-run triage on #7713 via workflow_dispatch to confirm it now produces the stage comment.

.qwen/skills/triage/SKILL.md:57-61 — the guard fires only on unattended events (issues, pull_request_target); explicit reruns (workflow_dispatch / issue_comment) "run all stages" unconditionally. So step 2 passes identically with or without this rename. The discriminating check is an unattended event (issues: edited/reopened, or pull_request_target: opened) on a thread whose lifecycle comment already carries the new marker.


🔵 Nit — stage=rerun-summary is still inside the reserved namespace

qwen-triage.yml:619 still posts <!-- qwen-triage stage=rerun-summary -->. By this PR's own model ("the guard matches any comment containing <!-- qwen-triage stage="), that's the same collision, also emitted by infrastructure rather than the agent. The reachable path is narrow — rerun-summary posts only on issue_comment for PRs, and triage's only unattended PR trigger is pull_request_target: [opened, ready_for_review], so the thread would have to go draft → ready afterwards — so it isn't a blocker. But renaming it now, while the namespace convention is being set, closes the class rather than the instance.


🔵 Nit — the new parity test can pass vacuously

Mutation-tested scripts/tests/qwen-triage-finalize-workflow.test.js:148:

Mutation Result
Revert finalize STATUS_MARKER to the old value fails ✅ load-bearing for its stated purpose
Rename the shell var on both sides (all MARKER=LIFECYCLE_VAR=, STATUS_MARKER=LIFECYCLE_VAR=) passes ❌ both regexes yield undefined; expect(undefined).toBe(undefined)
Drift only the second MARKER= in qwen-triage.yml (:640) passesString.match without /g reads the first match only

The third is caught by the sibling assertion in qwen-triage-workflow.test.js:228, so suite coverage holds — but the parity test under-delivers on its name. /MARKER='([^']+)'/ is also unanchored, so it silently retargets to APPROVE_MARKER=/STATUS_MARKER= if either ever moves earlier in the file (verified: prepending a STATUS_MARKER= line to qwen-triage.yml makes it extract that value instead).

const triageStatusMarker = triageWorkflowText.match(/^\s*MARKER='([^']+)'/m)?.[1];
// pin the literal too — anchoring alone doesn't survive a two-sided rename
expect(triageStatusMarker).toBe('<!-- qwen-triage-lifecycle -->');
expect(finalizeStatusMarker).toBe(triageStatusMarker);

Verdict

Right diagnosis, right shape, applied consistently across all three emit sites — and the three inline comments you dismissed really are stale. The one thing still outstanding is the autofix filter regression, which the 20:42 review raised and which I reproduce independently: renaming the marker silently reclassifies the lifecycle comment as actionable reviewer feedback in a different workflow. That's a one-line change at 4 sites, tests stay 137/137. Worth folding in the legacy-marker fallback too, since without it the fix never reaches the 35 open threads (including #7712 / #7713) that still carry the old marker.

中文说明

基于 head bf197afb 的复审(自上轮以来未变)

首先,关于「三条评论全是误报」的回复——这三条你说得对。 它们是 16:01/17:26 UTC 的 bot 行内评论,86fe06cb / 74fb91d5 / bf197afb 三个提交确实已经全部修复。在当前 head 上复核:

被驳回的意见 bf197afb 上的状态
测试仍断言旧标记 ✅ 已过时——qwen-triage-workflow.test.js:216,231 断言的是新标记
finalize.yml:95 仍是旧标记 ✅ 已过时——STATUS_MARKER='<!-- qwen-triage-lifecycle -->'
finalize 测试未固定 STATUS_MARKER ✅ 已过时——:148 已新增一致性测试

最新的顶层审阅(CHANGES_REQUESTED,2026-07-25 20:42 UTC,提交时间晚于 19:53 的 bf197afb)是另一条发现,并不在这三条之列。它目前仍然成立,下面是我独立复现的结果。当前 head 上已不存在任何 stage=status,因此重命名本身是彻底且正确的——问题出在另一个 workflow 中的消费方。

🔴 阻塞项 —— 重命名破坏了 qwen-autofix 的 bot 评论过滤(4 处)

.github/workflows/qwen-autofix.yml:2292、2733、2818、2895 使用「候选分支 + 一个字面空格」过滤非可执行的 bot 评论。旧标记 <!-- qwen-triage stage=status --> 能匹配(qwen-triage 后跟空格),而 <!-- qwen-triage-lifecycle --> 在该位置是连字符,匹配失败。旧标记的形状本身就是该消费方依赖的契约。

以真实过滤串与真实评论正文、用 jq(与 workflow 同一引擎)验证:旧标记 → true(被过滤);新标记 → false未被过滤,回归);stage=1true(不受影响)。

该正则是此处唯一的防线: 生命周期评论的作者为 qwen-code-ci-bot,即 REVIEW_BOT:103)且不是 AUTOFIX_BOT:97),其 author_associationCOLLABORATORTRUSTED_ASSOC:107),因此会同时通过登录名过滤与提示注入信任门禁两道关卡。

故障场景: autofix bot 开启 PR → triage 在 pull_request_target: opened 触发 → 发布生命周期评论 → autofix 下一次扫描将其计入 N_ISSUE_COMMENTS → 白白消耗一整轮 agent 去「处理」一条 🔄 Qwen Triage is running 通知。同一正文还会流经 :2733(推进 NEWEST 水位线)与 :2818/:2895(破坏 LIVE_NEW 的重复运行抑制)。

修复: 在四处候选分支中加入 qwen-triage-lifecycle,并保持 qwen-triage 在前,以免破坏 qwen-autofix-workflow.test.js:5714 现有的前缀断言(oniguruma 会回溯,两种顺序均可匹配)。本地在四处应用后验证:新标记 → truestage=1true,且 qwen-autofix-workflow + qwen-triage-workflow + qwen-triage-finalize-workflow137/137 全部通过(与该 head 上 137/137 的基线一致)。

🟡 建议修复 —— 修复不具追溯性,历史线程仍存在冲突

抽样最近更新的 60 个开放 issue 与 60 个开放 PR,仅统计由 bot 发布且正文以标记开头的评论(避免审阅正文中引用的标记造成虚高):

合并后这些线程仍保留旧标记与冲突,且 upsert 找不到它们:qwen-triage.yml:319-324 会新建第二条评论,而 qwen-triage-finalize.yml:323-326 会 fail-closed([ -n "$sid" ] || return 0)静默跳过更新,留下永久的「🔄 运行中」。这还会与上述阻塞项叠加:每个重新 triage 的线程都会新增一条不再被过滤的生命周期评论。

在每个 upsert 处各加两行回退查找即可自愈(PATCH 正文始终写入 $MARKER,因此首次重跑就会把旧标记改写掉)。

🟡 建议修复 —— 测试计划第 2 步不具区分度

.qwen/skills/triage/SKILL.md:57-61 明确:守卫仅在非托管事件(issuespull_request_target)下触发;显式重跑(workflow_dispatch / issue_comment)「会跑完所有 stage」。因此无论是否有本次重命名,第 2 步都会同样通过。唯一具区分度的检查是在非托管事件(issues: edited/reopened,或 pull_request_target: opened)下、针对生命周期评论已带新标记的线程进行验证。

🔵 细节 —— stage=rerun-summary 仍在保留命名空间内

qwen-triage.yml:619 仍然发布 <!-- qwen-triage stage=rerun-summary -->。按本 PR 自述的模型,这是同一类冲突,同样由基础设施而非 agent 发布。可达路径较窄(rerun-summary 仅在 PR 的 issue_comment 下发布,而 triage 唯一的非托管 PR 触发器是 pull_request_target: [opened, ready_for_review],需要线程之后经历 draft → ready 的转换),因此不算阻塞。但趁命名空间约定正在确立时一并改名,可以关掉整个类别而非单个实例。

🔵 细节 —— 新增的一致性测试可能空转通过

scripts/tests/qwen-triage-finalize-workflow.test.js:148 做变异测试:

变异 结果
把 finalize 的 STATUS_MARKER 改回旧值 失败 ✅ 对其既定目的有效
两侧同时重命名 shell 变量(全部 MARKER=LIFECYCLE_VAR=STATUS_MARKER=LIFECYCLE_VAR= 通过 ❌ 两个正则均返回 undefinedexpect(undefined).toBe(undefined)
只改动 qwen-triage.yml 中第二处 MARKER=:640 通过 ❌ 未加 /gString.match 只读取第一个匹配

第三项会被 qwen-triage-workflow.test.js:228 的同类断言捕获,整体套件覆盖没有漏洞——但这个一致性测试本身没有兑现它的名字。另外 /MARKER='([^']+)'/ 未加锚定,一旦 APPROVE_MARKER= / STATUS_MARKER= 挪到文件更靠前的位置就会静默匹配到错误目标(已验证:在 qwen-triage.yml 开头插入一行 STATUS_MARKER= 后,它提取到的就是该值)。建议改用 /^\s*MARKER='([^']+)'/m 并同时断言字面量——仅加锚定无法抵御两侧同时重命名。

结论

诊断正确、方案方向正确,三处发布点的重命名也应用得很彻底;你驳回的那三条行内意见确实已经过时。目前仍未解决的只有 autofix 过滤回归——20:42 那条审阅提出过,我也独立复现了:重命名会在另一个 workflow 中把生命周期评论静默地重新归类为「可执行的审阅反馈」。这是 4 处各一行的改动,测试仍保持 137/137。同时建议一并加入旧标记回退查找,否则修复无法覆盖仍带旧标记的 35 个开放线程(包括 #7712 / #7713)。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Approve to Comment: CI failing: Test (ubuntu-latest, Node 22.x). Reviewed.

中文说明

⚠️ 已从批准降级为评论:CI failing: Test (ubuntu-latest, Node 22.x)。 已审查。

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Re-review at head af675e70 — my earlier blocker is cleared

The qwen-triage-lifecycleqwen-triage lifecycle change (space, not hyphen) resolves the cross-workflow regression I raised in the last two rounds, and it does so more cleanly than the fix I proposed — no edit to qwen-autofix.yml is needed at all. Re-ran the same oracle:

filter: <!-- (autofix-eval|autofix-rearm|autofix-base-updated|qwen-triage|
              qwen-review-suggestion-summary|pr-force-push|qwen-review-ack) 

<!-- qwen-triage stage=status -->          filtered_as_bot_noise=true   (old)
<!-- qwen-triage-lifecycle -->             filtered_as_bot_noise=false  ← R1/R2 blocker
<!-- qwen-triage lifecycle -->             filtered_as_bot_noise=true   ← this head ✅
<!-- qwen-triage stage=1 -->               filtered_as_bot_noise=true

Also verified good:

  • Migration is complete for in-repo consumers. Repo-wide grep: stage=status now appears only in the two workflows (as the legacy fallback) and the tests. Nothing else reads it.
  • Legacy reuse rewrites in place, so the migration itself creates no duplicate status comments. Population scan over the 60 most-recently-updated threads: 45 still carry a legacy comment that will migrate on next touch.
  • 136/136 pass at head on qwen-triage-workflow + qwen-triage-finalize-workflow + qwen-autofix-workflow (15 + 19 = 34 on the two focused files, matching your test plan). eslint clean.
  • Prettier warns on qwen-triage.yml, but identically at merge-base 596abd96 — pre-existing, not introduced here. Ignore it.

What follows is not about the rename, which is right. It's that the marker lookup the PR is editing has a defect that already caused visible damage on this very thread, and the legacy-OR widens it.


1. [Critical] The lookup in qwen-triage.yml has no author filter — it currently resolves to a human's comment on this PR

Both edited steps select with contains() and no .user.login filter. Replaying the shipped jq against this thread's live comments:

$ gh api repos/QwenLM/qwen-code/issues/7723/comments --method GET --paginate -F per_page=100 \
  | jq -rs --arg m '<!-- qwen-triage lifecycle -->' \
           --arg legacy '<!-- qwen-triage stage=status -->' \
    '[.[][] | select((.body|contains($m)) or (.body|contains($legacy)))] | last | .id // empty'
5081415404

5081415404 is my review comment, authored by wenshao. The full match set:

id author starts with marker? first line
5079059925 qwen-code-ci-bot <!-- qwen-triage stage=status -->
5079073949 qwen-code-ci-bot <!-- qwen-triage stage=2 -->
5079075537 qwen-code-ci-bot <!-- qwen-triage stage=status -->
5081237171 wenshao ## Review: rename triage status marker
5081415404 wenshao ## Re-review at head ...

So the next triage run on this PR would PATCH my comment with "🔄 Qwen Triage is running". The bot token has repo write, and GitHub lets write-access collaborators edit others' issue comments, so that PATCH likely succeeds; if it 403s instead, the run silently posts no status at all. Both outcomes are bad.

qwen-triage-finalize.yml already does this correctly — select(.user.login == $bot) — and its own inline comment states the rationale:

# ... (a forged marker in a third-party comment is ignored by the author filter).

The two files disagree, and this PR touches exactly the lines where they disagree. The repo's own skill mandates the filter too (.qwen/skills/triage/references/pr-workflow.md:28):

Resolve the comment id by its stage marker AT PATCH TIME ... The author filter matters too — the marker is public text anyone can paste into a comment, and the bot PAT may be able to edit other users' comments

This is pre-existing (it arrived with #7654), but the PR widens it: OR-ing in the legacy marker makes every thread where anyone ever quoted <!-- qwen-triage stage=status --> a mis-target — and during a marker migration, that string is precisely what gets quoted in review prose.

⚠️ Heads-up: because this comment quotes both markers verbatim (unavoidable, if the fix is to be copy-pasteable), this comment now becomes the newest match and will be the one a future triage run overwrites. That is the finding demonstrating itself. Don't be surprised if it turns into "🔄 Qwen Triage is running" before the fix lands.

2. [Critical] contains should be startswith — this already destroyed the Stage 3 comment on this PR

Note row 5079073949 above: the Stage 2 comment matches the lookup because it quotes the marker in its body. Any bot comment that mentions the marker is a candidate for being overwritten.

The timeline of run 30163690366 (the only triage run this branch ever had, conclusion: success):

time event
15:29:11 "Post triage status comment" creates 5079059925 — "🔄 running". updated_at never moves.
15:32:45 agent posts Stage 1
15:33:08 agent posts Stage 2
15:33:38 5079075537 is created — the Stage 3 slot, 30s after Stage 2
15:34:01.98 "Finalize triage status comment" step starts (per run log)
15:34:02 5079075537 is updated — it now carries the finalize "✅ finished" body

The finalize step cannot have created a comment 23 seconds before it ran. It resolved EXISTING_ID to the last match — 5079075537, newer than the real status comment — and PATCHed an agent-authored comment. That is why this PR has a stale "🔄 running" comment that never finalized, a duplicate "✅ finished" comment, and no Stage 3 comment.

Control for the missing Stage 3, over the 60 most-recently-updated threads:

threads with a Stage 2 comment: 36
  of those, WITH Stage 3:    33
  of those, WITHOUT Stage 3: 3  -> #7723, #7383, #6972

Caveat on this one: GitHub exposes no comment edit history via the API, so I cannot read 5079075537's pre-edit body to prove byte-for-byte that it was Stage 3. The inference rests on the creation timestamp landing in the Stage 3 slot, the edit timestamp matching the finalize step exactly, and Stage 3 being absent. The skill notes the same class of incident has happened before: "observed on a real re-run: the stage=3 comment clobbered with stage=1 content mid-run."

Every body these steps write begins with the marker, so startswith is a strict improvement with no loss of matching.

Suggested fix

qwen-triage.yml, both steps (:311 and :638):

MARKER='<!-- qwen-triage lifecycle -->'
LEGACY_MARKER='<!-- qwen-triage stage=status -->'
if ! BOT_LOGIN="$(gh api user --jq '.login')" || [ -z "$BOT_LOGIN" ]; then
  echo "::warning::Cannot resolve the bot identity; skipping the status comment." >&2
  exit 0
fi
...
  jq -rs --arg m "$MARKER" --arg legacy "$LEGACY_MARKER" --arg bot "$BOT_LOGIN" \
    '[.[][]
       | select(.user.login == $bot)
       | select((.body | startswith($m)) or (.body | startswith($legacy)))]
     | last | .id // empty'

qwen-triage-finalize.yml:325 — author filter is already there, just anchor it:

-'[.[] | select(.user.login == $bot) | select((.body | contains($m)) or (.body | contains($legacy)))] | last | .id // empty'
+'[.[] | select(.user.login == $bot) | select((.body | startswith($m)) or (.body | startswith($legacy)))] | last | .id // empty'

3. [Suggestion] The new qwen-triage-workflow.test.js assertions are vacuous

Mutation: swap the values of MARKER and LEGACY_MARKER at both emit sites, so the workflow emits <!-- qwen-triage stage=status --> again — i.e. this PR's entire fix reverted:

MARKER='<!-- qwen-triage stage=status -->'      # emitted
LEGACY_MARKER='<!-- qwen-triage lifecycle -->'  # fallback

15/15 still pass. A bare toContain('<!-- qwen-triage lifecycle -->') cannot distinguish the emitted marker from the legacy fallback, since both literals are present either way.

Your finalize test does it correctly and fails under the identical mutation (1 failed / 19):

expect(script).toContain("STATUS_MARKER='<!-- qwen-triage lifecycle -->'");

Mirror that shape in qwen-triage-workflow.test.js:216 / :229:

-expect(statusStep).toContain('<!-- qwen-triage lifecycle -->');
-expect(statusStep).toContain('<!-- qwen-triage stage=status -->');
+expect(statusStep).toContain("MARKER='<!-- qwen-triage lifecycle -->'");
+expect(statusStep).toContain("LEGACY_MARKER='<!-- qwen-triage stage=status -->'");

4. [Suggestion / follow-up] stage=rerun-summary is the same collision class

qwen-triage.yml:620 still posts <!-- qwen-triage stage=rerun-summary --> — a non-stage lifecycle comment inside the reserved stage= namespace, exactly the shape this PR is removing. The duplicate guard exits unattended runs on a "prior <!-- qwen-triage stage=N --> marker", and rerun-summary is no more a stage number than status was. Out of your stated scope, and it does not block this PR, but the class isn't closed until that one moves too. (@gwinthis raised this earlier as well.)


Verdict

The rename is correct, complete, and clears my R1/R2 blocker — that part is ready. Findings 1 and 2 are pre-existing rather than introduced here, but I'd land them with this PR rather than after it: the legacy-OR widens exactly the surface that already cost this thread its Stage 3 comment, and the fix is ~6 lines in the two blocks you're already editing. Finding 3 is a small change that makes the test actually defend the fix.

中文说明

先说结论:我前两轮提的阻塞项已经解决

qwen-triage-lifecycleqwen-triage lifecycle(用空格而非连字符)修好了跨 workflow 的回归,而且比我当初建议的方案更干净——完全不需要动 qwen-autofix.yml。用同一个 oracle 复验:新 marker 与旧 marker 一样能被 autofix 过滤器识别为 bot 噪声。

其他已验证通过的部分:仓库内消费方迁移完整(stage=status 现在只剩两个 workflow 的 legacy 兜底和测试);旧评论原地改写,迁移本身不会产生重复评论(最近 60 个线程中 45 个带旧 marker,会在下次触碰时迁移);head 上 136/136 通过,两个聚焦文件 15+19=34,与你的测试计划一致;eslint 干净;prettier 对 qwen-triage.yml 的告警在 merge-base 596abd96 上完全相同,属于既有问题,不是本 PR 引入。

下面几点不是针对重命名本身——重命名是对的——而是这个 PR 正在修改的那段查找逻辑有缺陷,且已经在本线程上造成了可见的损坏,而 legacy 兜底把这个面放大了。

1.〔严重〕qwen-triage.yml 的查找没有作者过滤,目前会命中一条人类评论。 用 PR 里的 jq 跑本线程的真实评论,EXISTING_ID 解析出 5081415404——那是我自己(wenshao)的 review 评论。下次 triage 运行会把它 PATCH 成「🔄 Qwen Triage is running」。bot token 有仓库写权限,GitHub 允许写权限协作者编辑他人评论,所以这个 PATCH 很可能会成功;即便 403,也只是变成静默不发状态评论。两种结果都不好。qwen-triage-finalize.yml 已经做对了(select(.user.login == $bot)),其代码注释还专门说明了理由;skill 文档 pr-workflow.md:28 也明确要求作者过滤,原因正是「marker 是任何人都能粘贴的公开文本,而 bot PAT 可能有权编辑他人评论」。这个问题源自 #7654 而非本 PR,但本 PR 放大了它:OR 进 legacy marker 后,任何曾经引用过 <!-- qwen-triage stage=status --> 的线程都会误命中——而在 marker 迁移期间,这个字符串恰恰最容易被 review 正文引用。

2.〔严重〕contains 应改为 startswith——它已经吃掉了本 PR 的 Stage 3 评论。 匹配集合里 Stage 2 评论(5079073949)之所以命中,只是因为正文里引用了该 marker。运行 30163690366(本分支唯一一次 triage,成功)的时间线:15:29:11 创建「🔄 running」评论(此后再未更新);15:33:08 发 Stage 2;15:33:38 创建评论 5079075537(正好是 Stage 3 的位置);15:34:01.98 finalize 步骤启动;15:34:02 该评论被更新成 finalize 的「✅ finished」正文。finalize 不可能创建一条比它自己早 23 秒的评论——它取了「最后一条匹配」,即 5079075537,然后覆盖了一条agent 写的评论。这就是本 PR 为何有一条永远停在「running」的评论、一条重复的「finished」评论,以及没有 Stage 3 评论。对照组:最近 60 个线程中有 Stage 2 的共 36 个,其中 33 个有 Stage 3,缺失的只有 3 个(含 #7723)。

此项的保留: GitHub 的 API 不暴露评论编辑历史,所以我无法逐字证明 5079075537 原本就是 Stage 3。推断依据是创建时间落在 Stage 3 的位置、编辑时间与 finalize 步骤精确吻合、以及 Stage 3 确实缺失。skill 文档记录过同类事故:「曾观察到 stage=3 评论在运行中被 stage=1 内容覆盖」。这两个步骤写出的正文都以 marker 开头,所以 startswith 是只赚不亏的收紧。修复建议见上方英文部分的代码块。

3.〔建议〕qwen-triage-workflow.test.js 新增的断言是空转的。 变异实验:把两处 MARKERLEGACY_MARKER值对调,让 workflow 重新发出 <!-- qwen-triage stage=status -->——也就是把本 PR 的修复整个还原——结果 15/15 仍然全绿。裸的 toContain('<!-- qwen-triage lifecycle -->') 无法区分「发出的 marker」和「legacy 兜底」,因为两个字面量在两种情况下都存在。你的 finalize 测试写法是对的,在同样的变异下会失败(1 failed / 19)。建议在 :216 / :229 采用同样的、带变量名限定的断言。

4.〔建议 / 后续〕stage=rerun-summary 属于同一类冲突。 qwen-triage.yml:620 仍在保留的 stage= 命名空间里发一条非阶段性的生命周期评论,形状与本 PR 要移除的完全一致。不在你声明的范围内,也不阻塞本 PR,但这一类问题要等它一起挪走才算关闭。(@gwinthis 此前也提过。)

结论

重命名本身正确、完整,并且解除了我 R1/R2 的阻塞项——这部分可以合。第 1、2 项是既有问题而非本 PR 引入,但我建议随本 PR 一起修,而不是留到之后:legacy 兜底恰好放大了那个已经让本线程丢掉 Stage 3 评论的面,而修复只需在你已经在改的两个代码块里加约 6 行。第 3 项是个小改动,能让测试真正守住这个修复。

@yiliang114

yiliang114 commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline.

中文说明

已审查。 建议见行内评论。

— qwen3.7-max via Qwen Code /review

Comment thread scripts/tests/qwen-triage-workflow.test.js Outdated
Comment thread .github/workflows/qwen-triage-finalize.yml Outdated

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] qwen-triage.yml comment lookup has no author filter — PR widens the match surface. The two edited steps (~:323, ~:690) use contains() without .user.login == $bot. After the legacy-OR, any comment quoting either marker string becomes a match target. qwen-triage-finalize.yml already filters correctly. Failure scenario: a human reviewer quoting the marker in a review comment gets their comment overwritten by the bot PATCH. (Existing blocker from @wenshao's review — still stands at this commit.)

[Critical] qwen-triage.yml uses contains() instead of startswith() for marker matching — pre-existing defect already demonstrated on this PR. The finalize step resolved EXISTING_ID to a bot-authored Stage comment that merely quoted the marker, overwriting it and destroying the Stage 3 result. Every body these steps write begins with the marker, so startswith is a strict improvement. (Existing blocker from @wenshao's review — still stands at this commit.)

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Not reviewed: verification — the review posts findings, but no verifier was launched with a prompt this skill builds — they were ruled on, if at all, without the verdict bar its brief carries. [Critical] qwen-triage.yml:323,690 — comment lookup lacks author filter (select(.user.login == $bot)). qwen-triage-finalize.yml:325 correctly filters, but qwen-triage.yml's two status-comment lookups match any comment containing the marker. The legacy-OR widens the match surface: any comment quoting either marker becomes a match target. The project's pr-workflow.md:28 mandates the filter. Failure scenario: bot PATCH overwrites a human reviewer's comment that quoted the marker. (Existing blocker from @wenshao's review — still stands at this commit.) [Critical] qwen-triage.yml and qwen-triage-finalize.yml — both use contains() instead of startswith() for marker matching. Every body these steps write begins with the marker, so startswith is a strict improvement. Pre-existing defect already demonstrated on this PR: the finalize step resolved EXISTING_ID to a bot-authored Stage comment that merely quoted the marker, overwriting it and destroying the Stage 3 result. (Existing blocker from @wenshao's review — still stands at this commit.)

中文说明

已审查。 未审查:验证——本次评审发布了发现,但没有验证 agent 是用本 skill 构建的 prompt 启动的——这些发现即便被裁定过,也缺失了 brief 承载的裁定标准。 [Critical] qwen-triage.yml:323,690 — comment lookup lacks author filter (select(.user.login == $bot)). qwen-triage-finalize.yml:325 correctly filters, but qwen-triage.yml's two status-comment lookups match any comment containing the marker. The legacy-OR widens the match surface: any comment quoting either marker becomes a match target. The project's pr-workflow.md:28 mandates the filter. Failure scenario: bot PATCH overwrites a human reviewer's comment that quoted the marker. (Existing blocker from @wenshao's review — still stands at this commit.) [Critical] qwen-triage.yml and qwen-triage-finalize.yml — both use contains() instead of startswith() for marker matching. Every body these steps write begins with the marker, so startswith is a strict improvement. Pre-existing defect already demonstrated on this PR: the finalize step resolved EXISTING_ID to a bot-authored Stage comment that merely quoted the marker, overwriting it and destroying the Stage 3 result. (Existing blocker from @wenshao's review — still stands at this commit.)

— qwen3.7-max via Qwen Code /review

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] qwen-triage.yml:324,691 — comment lookup lacks author filter (select(.user.login == $bot)). qwen-triage-finalize.yml:325 correctly filters, but qwen-triage.yml's two status-comment lookups match any comment containing the marker. The legacy-OR widens the match surface: any comment quoting either marker becomes a match target. Existing blocker from @wenshao's review — still stands at this commit.

[Critical] qwen-triage.yml and qwen-triage-finalize.yml — both use contains() instead of startswith() for marker matching. Every body these steps write begins with the marker, so startswith is a strict improvement. Pre-existing defect already demonstrated on this PR: the finalize step resolved EXISTING_ID to a bot-authored Stage comment that merely quoted the marker, overwriting it and destroying the Stage 3 result. Existing blocker from @wenshao's review — still stands at this commit.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Re-review at head 3edf3b8f — rename is sound; two one-line hardenings left

Every marker literal in this comment — prose and code blocks — uses a non-breaking hyphen (<!‑‑ …) on purpose. As shown below, quoting the real marker makes your own comment the next PATCH target; that is exactly how this thread ended up pointing at a human comment. Retype the hyphens as ASCII when copying the snippets.

Verdict: the rename itself is correct and verified — I consider my earlier blocker closed. What remains are the two lookup-hardening items that three reviewers have now flagged; both are pre-existing, but this PR edits the exact expressions and one of them has a live mis-target on this very thread.


✅ What this round confirms as fixed

1. The autofix-filter regression I blocked on at R1/R2 is genuinely gone. Moving to the space form (<!‑‑ qwen-triage lifecycle ‑‑>) rather than the hyphen form keeps the marker inside autofix's non-actionable filter with no qwen-autofix.yml change at all — cleaner than the alternation edit I originally proposed. Re-ran the oracle against the shipped filter at qwen-autofix.yml:2292/2733/2818/2895:

marker matched by <!‑‑ (…|qwen-triage|…) (true = correctly ignored)
<!‑‑ qwen-triage stage=status ‑‑> (old) true
<!‑‑ qwen-triage lifecycle ‑‑> (this PR) true
<!‑‑ qwen-triage-lifecycle ‑‑> (earlier head) false ← the regression, now avoided

2. The test vacuity I raised last round is fixed, and I mutation-verified it. At the previous head, swapping the values of MARKER/LEGACY_MARKER (a full semantic revert of the fix) left qwen-triage-workflow.test.js 15/15 green. With 3edf3b8's variable-qualified pins, the same mutation applied globally to all 3 assignment sites now fails 2 tests across both files:

× qwen-triage tmux workflow > posts an early live-progress status comment and finalizes the same one
× qwen-triage-finalize workflow > binds every marker to the full head SHA of the triggering run
  Tests  2 failed | 34 passed (36)

Clean run at head: 138/138 on qwen-autofix-workflow + qwen-triage-workflow + qwen-triage-finalize-workflow.

3. The legacy-migration path is load-bearing, and it works. Scanning the 50 most-recently-updated open threads: 30 of 50 still carry a bot-authored legacy stage=status comment (#7710, #7724, #7739, #7749, #7731, #7725, #7729, #7720, #7702, #7713, #7714, #7726, …). The legacy-OR resolves those correctly, so they get rewritten in place rather than duplicated. This part of the design is well judged.

4. Not PR-introduced (checked against merge-base 0f56e35c, so nobody needs to chase these): the 3 SC2016:info actionlint findings are identical at base and are invisible in CI anyway (scripts/lint.js runs actionlint with -shellcheck=).


🔴 Remaining — worth fixing while you are already in these two expressions

Finding 1 — qwen-triage.yml's two lookups have no author filter, and this thread is already mis-targeted

qwen-triage.yml:323 and :690 select on body content alone. qwen-triage-finalize.yml:325 does it correctly and even documents why. Running the shipped jq from this PR's head against this PR's own live comment thread:

EXISTING_ID = 5082384432
  author:  yiliang114      ← the PR author, a human
  created: 2026-07-26T06:35:46Z
  first line: <!‑‑ resolve-pr-comments ‑‑>

That comment matched only because it describes the change in prose:

- Changed the lifecycle marker to ⟨marker⟩, which remains distinct from stage results…
- Kept compatibility with existing ⟨legacy marker⟩ comments…

The next triage run on #7723 would PATCH your own reply and destroy it. 7 comments match; only 2 are real status comments — 5 of 7 are false positives. The correct recipe (select(.user.login == $bot) + startswith) picks 5079075537, the actual lifecycle comment.

This is not hypothetical scope creep: the same test file already mandates the recipe. qwen-triage-workflow.test.js:391 asserts select(.user.login == $bot), startswith($m), and explicitly not.toContain('contains($m)') for the skill's stage_comment_id(), citing the #7693 incident where a re-run clobbered the stage=3 comment. pr-workflow.md:28 says it in words: "the marker is public text anyone can paste into a comment, and the bot PAT may be able to edit other users' comments." The workflow's own status lookup is the one place that does not follow it.

Fix, one line per site:

[.[][] | select(.user.login == $bot)
       | select((.body | startswith($m)) or (.body | startswith($legacy)))] | last | .id // empty

(BOT_LOGIN is already resolved in qwen-triage-finalize.yml; qwen-triage.yml would need the same gh api user --jq '.login' line, failing closed if it cannot be resolved.)

Finding 2 — containsstartswith (both files) — real hardening, but let me calibrate the severity

I want to be straight about the evidence rather than echo the "Critical" label. Across those same 30 threads, the finalize path (which does author-filter) mis-targets 0 times today. Every body these steps write begins with the marker, so startswith is a strict improvement with no downside — but on current data it is defense-in-depth, not an actively-firing bug. The one place contains bites right now is in combination with the missing author filter (Finding 1). Worth doing in the same patch; not worth blocking on by itself.

For the record, my earlier claim that this already destroyed #7723's Stage 3 comment remains circumstantial — it rests on timestamps plus a missing stage=3, and GitHub exposes no comment edit history, so I cannot prove it. Please don't treat it as established.

Finding 3 — the PR's own thesis is only half-applied

qwen-triage.yml:655 still posts <!‑‑ qwen-triage stage=rerun-summary ‑‑> — infrastructure metadata occupying the reserved stage= namespace, the identical collision class this PR exists to eliminate. It is lower-frequency (re-runs only), but once posted it sits in the thread permanently, so a later unattended event sees it. Renaming it in the same pass (e.g. <!‑‑ qwen-triage rerun-summary ‑‑>, which keeps the autofix trailing-space compatibility) makes the fix complete. Reasonable to defer to a follow-up if you'd rather keep this PR tight — but then it's worth saying so explicitly in the PR body.


🟡 Nits

  • scripts/tests/qwen-triage-workflow.test.js:235-237 is not prettier-clean. The finalizeStep assertion is split across 3 lines but fits in 80 columns. Baseline at merge-base is clean, so this is PR-introduced. This will not turn CI redscripts/lint.js --prettier runs prettier --write . and never fails — but it will dirty the tree for anyone running npm run format. One-line fix:
    expect(finalizeStep).toContain("MARKER='<!‑‑ qwen-triage lifecycle ‑‑>'");
  • qwen-triage-finalize-workflow.test.js:90-95: the authorFiltered count regex no longer matches the status-comment lookup (its shape changed), so the population it counts dropped 4 → 3 while the assertion stays >= 3 — it still passes, but the comment above it ("CI-region lookup, approve-marker lookup, and status-comment lookup") now names a lookup the regex no longer sees. The new explicit toContain covers it, so this is cosmetic; consider updating the comment or tightening to toBe(3).
  • One design note: the legacy-OR is permanent widening. Once the 30 threads self-migrate it is dead weight that keeps matching the old string forever. Pairing it with startswith bounds that; a dated TODO to drop the OR would be even better.

Verification performed

Isolated worktree at 3edf3b8f, node_modules symlinked from a built checkout.

  • npx vitest run --config ./scripts/tests/vitest.config.ts qwen-autofix-workflow qwen-triage-workflow qwen-triage-finalize-workflow138/138
  • Mutation A/B: global value-swap of all 3 MARKER=/LEGACY_MARKER= assignment pairs → 2 failed / 34 passed (was 15/15 green at the prior head)
  • jq oracle: new/old/hyphen marker forms vs. the 4 shipped autofix non-actionable filters
  • Live oracle: shipped lookup vs. issues/7723/comments (11 comments) and vs. 50 recent open threads via GraphQL
  • prettier --check at head vs. merge-base 0f56e35c; actionlint at head vs. merge-base
中文说明

结论:重命名本身正确且已验证通过,我之前提出的阻塞项可以关闭。剩下两处一行即可完成的加固。

已确认修复的部分

  1. R1/R2 的 autofix 过滤器回归确实已解决。 改用空格形式(<!‑‑ qwen-triage lifecycle ‑‑>)而非连字符形式,使 marker 仍能被 autofix 的 non-actionable 过滤器匹配,且完全不需要改动 qwen-autofix.yml——比我最初建议的 alternation 方案更干净。旧 marker true、本 PR 新 marker true、连字符形式 false(即被避免的那个回归)。
  2. 我上一轮指出的测试空转问题已修复,并已用变异测试确认。 上一版 head 下,交换 MARKER/LEGACY_MARKER 的取值(等同于完整回退该修复)测试仍 15/15 全绿3edf3b8 加上带变量名的字面量断言后,同样的全局变异会让两个文件共 2 个测试失败(2 failed / 34 passed)。head 干净运行为 138/138
  3. legacy 迁移路径确实关键,且工作正常。 扫描最近更新的 50 个开放线程,其中 30 个仍带有 bot 发布的旧 stage=status 评论,legacy-OR 能正确定位并原地改写,不会产生重复评论。这部分设计判断得当。
  4. 并非本 PR 引入(已对比 merge-base 0f56e35c):3 个 SC2016:info actionlint 提示在 base 上完全相同,且 CI 中 actionlint 以 -shellcheck= 运行,根本不会显示。

剩余问题

  • 问题 1(建议本次一并修复): qwen-triage.yml:323:690 的查找缺少作者过滤,仅按正文内容匹配;而 qwen-triage-finalize.yml:325 做法正确。用本 PR head 的实际 jq 跑本 PR 自己的评论线程,解析出的 EXISTING_ID = 5082384432,作者是 yiliang114(PR 作者本人,人类)——只因为该评论在正文中用散文描述了这次 marker 变更就被匹配上了。下一次 triage 运行会 PATCH 掉您自己的这条回复。 7 条命中中只有 2 条是真正的状态评论。同一个测试文件的第 391 行本来就强制要求 select(.user.login == $bot) + startswith($m) 并明确 not.toContain('contains($m)')pr-workflow.md:28 也用文字写明了原因。修复只需每处一行。
  • 问题 2(关于严重程度的客观说明): contains 改为 startswith 是严格意义上的改进且无副作用,但需要如实说明:在上述 30 个线程中,带作者过滤的 finalize 路径当前 0 次误命中。它目前属于纵深防御,而非正在发生的 bug;真正会立刻出问题的是它与问题 1 的组合。建议同一个 patch 一起改,但单独看不足以构成阻塞。另外,我此前关于「这已经毁掉了 fix(ci): rename triage status marker to avoid duplicate-guard collision #7723 的 Stage 3 评论」的说法仍属间接推断(依赖时间戳与缺失的 stage=3,而 GitHub 不提供评论编辑历史),请勿当作已证实的结论。
  • 问题 3: qwen-triage.yml:655 仍在发布 <!‑‑ qwen-triage stage=rerun-summary ‑‑>——同样是占用保留 stage= 命名空间的基础设施评论,与本 PR 要消除的冲突类别完全相同。频率较低(仅重跑时),但一旦发布就会长期留在线程中。若希望本 PR 保持精简,放到后续 PR 也合理,但建议在 PR 描述中明确说明。

小问题

  • scripts/tests/qwen-triage-workflow.test.js:235-237 不符合 prettier 格式(merge-base 是干净的,属本 PR 引入)。不会导致 CI 变红scripts/lint.js --prettier 执行的是 prettier --write .,从不失败),但会让执行 npm run format 的人产生额外 diff。
  • qwen-triage-finalize-workflow.test.js:90-95authorFiltered 计数正则已不再匹配状态评论查找,计数从 4 降到 3,而断言仍是 >= 3,因此仍然通过;但上方注释中列举的三项已与实际不符。新增的显式 toContain 已覆盖该场景,属表面问题。
  • legacy-OR 是永久性的匹配面扩大。等 30 个线程自行迁移完成后它就成了负担,却会永远继续匹配旧字符串。配合 startswith 可限定其影响范围,若能加一条带日期的 TODO 说明何时移除会更好。

Reviewed with Claude Opus 5 · isolated worktree at 3edf3b8f, live jq oracles against the GitHub API, and mutation A/B on the new assertions.

qwen-triage.yml's two status-comment lookups matched any comment containing
the marker — a human reviewer quoting the marker would have their comment
overwritten by the bot PATCH. Add select(.user.login == $bot) (already
present in qwen-triage-finalize.yml) and resolve BOT_LOGIN via gh api user.

Both workflows used contains() for marker matching; startswith() is a strict
improvement since every status comment body begins with the marker. This
prevents the demonstrated defect where the finalize step resolved EXISTING_ID
to a bot-authored Stage comment that merely quoted the marker.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline.

中文说明

已审查。 建议见行内评论。

— qwen3.7-max via Qwen Code /review

Comment thread .github/workflows/qwen-triage.yml Outdated
Comment thread .github/workflows/qwen-triage.yml Outdated
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@wenshao

wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Re-review at head 845d375d — both open findings fixed and mutation-verified; merge-ready

Marker literals in this comment use U+2011 non-breaking hyphens (<!‑‑ … ‑‑>) so that no marker lookup — including the one this PR is fixing — can ever resolve to this comment. Retype ASCII hyphens if you copy anything.

Previous round: #issuecomment-5083382191 (head 3edf3b8f). One new commit since: 845d375d fix(ci): add bot-author filter and startswith to triage status lookups.

Prior findings → status at this head

# Finding (round) Status at 845d375d
1 qwen-autofix.yml filter escape from the hyphen-form marker (R1/R2) ✅ Cleared at af675e70re-tested here, still clears
2 Test vacuity: bare marker literal can't tell emit from legacy (R3 #4) ✅ Fixed at 3edf3b8fre-tested here, mutation still dies
3 qwen-triage.yml's two lookups have no author filter (R3 #1 / R4) Fixed — verified by executing the step, not by reading it
4 contains should be startswith (R3 #2 / R4) Fixed at all 3 lookups
5 stage=rerun-summary is the same collision class (R3 #5) ⚪ Open — explicitly out of scope per the PR body; follow-up

The author-filter fix, verified by execution rather than by reading

I extracted both run: blocks with yaml.parse(...), put a fake gh on PATH that serves #7723's real 12-comment list verbatim, and ran the actual scripts — merge-base code vs. this head. The relevant ids: 5079059925 = bot status "running", 5079075537 = bot status "finished", 5082384432 = @yiliang114's own comment (a human's).

code Post-status step Finalize step
merge-base 0f56e35c0 PATCH 5082384432the author's comment PATCH 5082384432
head 845d375d PATCH 5079075537 ✅ the bot's own PATCH 5079075537

Population check over the 120 most-recently-updated issues+PRs (author.login == 'qwen-code-ci-bot' and body | startswith(marker), not contains — else review comments quoting the marker inflate every count):

  • 71/120 threads still carry a bot stage=status comment ⇒ the legacy-OR migration path is heavily load-bearing, and it resolves all 71 correctly.
  • Old jq mis-targets a non-status comment on 3/71 threads; new jq on 0/71. (The two remaining non-"running/finished" matches, on fix(cli): handle escaped dollars around inline math #7741 and feat(web-shell): persist terminal history pagination errors #7709, are legitimate "Deferred approval withheld" status bodies written by qwen-triage-finalize.yml — my classifier's gap, not a mis-target.)
  • 0/120 threads have a status body whose marker isn't the first bytes ⇒ moving to startswith strands nothing into a duplicate.

Migration ordering also holds: in the merged tree Post triage status comment is step 5 and Run Qwen Triage is step 7, so a legacy marker is rewritten before the agent evaluates the duplicate guard — exactly as the PR body claims.

Correcting my own R4 severity call on containsstartswith

At R4 I wrote that this one was "defense-in-depth — not an actively-firing bug" on the strength of a 0/30 present-day scan, and I called the Stage-3 destruction circumstantial. The present-day scan was right; the "circumstantial" label was too weak. Run 30163690366 closes it:

step  5  Post triage status comment    15:29:11 → 15:29:12
step  7  Run Qwen Triage               15:29:12 → 15:34:01
step  9  Notify silent triage re-run   SKIPPED          ← so not stage=rerun-summary
step 10  Finalize triage status comment 15:34:01 → 15:34:02
  • 5079059925 (the real status comment) has updated_at == created_at == 15:29:11finalize provably never touched it.
  • 5079075537 was created 15:33:38 — inside the agent step — and updated 15:34:02, exactly when finalize ran. Finalize cannot have created a comment 24 s before it started.
  • Its body is now precisely the 301-char finalize template (marker + EN + ZH, nothing else); PATCH replaces wholesale.
  • Only the triage job could author a bot issue comment in that window: review-pr didn't start until 15:39:18, and label / Classify PR / precheck all finished by 15:29:02.
  • There is no stage=3 comment on this thread, and no bot review between 15:29 and 15:34 (earliest is 16:01:10, from the separate review workflow — so it wasn't a terminal-gate exit either).

⇒ Finalize overwrote the agent's Stage 3 comment. Fingerprint across the population: 1/120 threads (this one), against 68 threads whose status comment was correctly updated in place. The mechanism is self-referential — the clobber needed a bot comment that quotes the marker, and the PR renaming the marker is the one PR whose Stage 3 discussed it. startswith closes exactly this. This is a point in the PR's favour, not a finding. (Still genuinely unprovable: the destroyed content — GitHub exposes no comment edit history via API.)

Findings — 2 test-coverage gaps and 1 nit. No correctness blocker.

Mutation matrix, 9 mutants against the two test files (baseline 36/36):

# Mutation Result
M1 post step: drop select(.user.login == $bot) ☠️ killed
M2 finalize step: drop the author filter ☠️ killed
M3/M4 startswithcontains in qwen-triage.yml (each step) ☠️ killed
M5 startswithcontains in qwen-triage-finalize.yml ☠️ killed
M6 post step: delete the BOT_LOGIN= resolution ☠️ killed
M8 swap MARKER/LEGACY_MARKER values at all 6 assignments (full semantic revert) ☠️ killed (2 tests)
M7 finalize step: delete the BOT_LOGIN= resolution 🧟 survived 36/36
M9 delete or (.body | startswith($legacy)) from both qwen-triage.yml lookups 🧟 survived 36/36

F1 — the legacy-migration path in qwen-triage.yml is untested (medium). M9 deletes the entire legacy-marker fallback from both lookups and the suite stays green. The tests pin that LEGACY_MARKER='…' is defined, never that it is used. That fallback is this PR's headline behavior ("legacy comments are reused and rewritten instead of … creating duplicate status comments") and it is load-bearing for 71/120 live threads. Note the asymmetry that makes this clearly an oversight rather than a choice: qwen-triage-finalize-workflow.test.js pins the whole jq expression, which is why the identical mutation there (M5) dies.

F2 — the finalize step's identity resolution is untested (low). M7 deletes BOT_LOGIN="$(gh api user …)" from the finalize step and the suite stays green, while the same deletion in the post step (M6) dies — the post-step block asserts gh api user --jq '.login' and the finalize block doesn't.

Both close with three assertions. I applied them and re-ran: baseline stays 36/36, and M7 and M9 both die.

// in the statusStep block
expect(statusStep).toContain('startswith($legacy)');
// in the finalizeStep block
expect(finalizeStep).toContain('startswith($legacy)');
expect(finalizeStep).toContain("gh api user --jq '.login'");

F3 — silent fallback when identity resolution fails, inconsistent with the sibling workflow (nit).

BOT_LOGIN="$(gh api user --jq '.login' 2>/dev/null || true)"

If that call fails, BOT_LOGIN is empty, select(.user.login == "") matches nothing, and both steps fall through to creating a new comment. Executed with the call forced to fail: post step → POST-NEW-COMMENT, finalize step → POST-NEW-COMMENT. That is fail-safe — it can never clobber, which is the right direction — but the finalize path then leaves the stale "🔄 running" comment in place and adds a second one, i.e. the exact two-comment state whose fingerprint I used above to detect the clobber. 2>/dev/null also hides the cause. qwen-triage-finalize.yml:98 already has the precedent worth mirroring:

if ! BOT_LOGIN="$(gh api user --jq '.login')" || [ -z "$BOT_LOGIN" ]; then
  echo "::warning::Cannot resolve the bot identity; skipping (markers cannot be author-verified)."
  exit 0
fi

Token identity is otherwise correct: both status steps run with GH_TOKEN: ${{ secrets.QWEN_CODE_BOT_TOKEN || secrets.CI_BOT_PAT }} — the same token that authors the comments — so gh api user resolves to the right login. The live "Deferred approval withheld" comments on #7741/#7709 prove that path already works in production.

Out of scope here — follow-ups, not reasons to hold this PR

  • stage=rerun-summary sits in the same stage= namespace; 7/120 threads carry one. Lower probability than stage=status was (it's only posted on re-runs, while the guard only exits on unattended events), but the same class.
  • .qwen/skills/triage/references/pr-workflow.md:32 still documents stage_comment_id() { # $1 = stage number (1, 2, 3) or "status". Dead text — the only call site is stage_comment_id 2 — but stale after this rename.
  • On main, not from this PR: feat(triage): add sandboxed /verify deep-verification lane #7710's /verify lane uses colon-form markers — <!‑‑ qwen-triage:verify ‑‑>, :verify-state=running, :verify-substantive — and all three escape autofix's <!‑‑ (…|qwen-triage|…) trailing-space filter. Oracle jq -n --arg b "$BODY" --arg f "$FILTER" '$b | test($f)': every space-form triage marker → true; all three colon-form markers → false. For a qwen-code-ci-bot comment that regex is the sole gate (REVIEW_BOT clears the association check at qwen-autofix.yml:2733), so autofix reads those status comments as actionable reviewer feedback. Same class as the hyphen form this PR correctly abandoned — worth its own PR.

Checks run

  • qwen-triage-workflow + qwen-triage-finalize-workflow + qwen-autofix-workflow at head: 138/138.
  • Trial-merged onto current main (7422bf879) — no conflicts, no auto-merge artifacts (marker assignments, both lookups, and BOT_LOGIN resolutions all verified intact in the merged file): 181/181.
  • actionlint -shellcheck= (as CI runs it) on both workflows: exit 0 at merge-base and at head. The 3 SC2016:info findings from the stricter shellcheck integration are identical in count at the merge-base — pre-existing, not PR-introduced.
  • bash -n and shellcheck -s bash -S warning on both extracted status blocks: clean.
  • Marker-shape oracle re-run at the merged tree: <!‑‑ qwen-triage lifecycle ‑‑> → filtered true (safe); hyphen form → false. The space form remains the right call, and it needed no qwen-autofix.yml edit at all.

Nothing here blocks the merge. F1 is the one I'd like landed with it — it's a single assertion guarding the behavior 71 live threads depend on.

中文说明

结论:845d375d — 两个遗留问题均已修复并通过变异验证,可以合并

本评论中的 marker 字面量使用 U+2011 不换行连字符(<!‑‑ … ‑‑>),确保任何 marker 查找(包括本 PR 正在修复的那个)都不会命中这条评论。若要复制请改回 ASCII 连字符。

上一轮:#issuecomment-5083382191(head 3edf3b8f)。新增一个提交 845d375d

历史问题 → 当前状态

# 问题(轮次) 845d375d 状态
1 连字符形式 marker 逃过 qwen-autofix.yml 过滤器(R1/R2) af675e70 已解决,本轮重新验证仍然通过
2 测试空洞:裸字面量无法区分 emit 与 legacy(R3 #4 3edf3b8f 已修,本轮重新验证变异仍被杀死
3 qwen-triage.yml 两处查找缺少作者过滤(R3 #1 / R4) 已修,通过实际执行脚本验证
4 contains 应为 startswith(R3 #2 / R4) 已修,三处查找全部修正
5 stage=rerun-summary 属同一冲突类(R3 #5 ⚪ 未处理 —— PR 已明确声明不在范围内,可后续跟进

用「执行」而非「阅读」验证作者过滤修复

我用 yaml.parse(...) 提取两个 run: 块,在 PATH 上放一个假 gh(原样返回 #7723 真实的 12 条评论),然后分别用 merge-base 与本 head 的脚本实际运行。相关 id:5079059925 = 机器人「running」状态评论,5079075537 = 机器人「finished」状态评论,5082384432 = @yiliang114 本人的评论(人类)。

代码 Post-status 步骤 Finalize 步骤
merge-base 0f56e35c0 PATCH 5082384432作者本人的评论 PATCH 5082384432
head 845d375d PATCH 5079075537 ✅ 机器人自己的 PATCH 5079075537

对最近更新的 120 个 issue+PR 做总量统计(按 author.login == 'qwen-code-ci-bot' body | startswith(marker),不能用 contains,否则引用了 marker 的评审评论会虚增计数):

  • 71/120 个线程仍带有机器人的 stage=status 评论 ⇒ legacy-OR 迁移路径承载了大量真实流量,且这 71 个都能被正确解析。
  • 旧 jq 在 3/71 个线程上命中非状态评论;新 jq 为 0/71。(fix(cli): handle escaped dollars around inline math #7741feat(web-shell): persist terminal history pagination errors #7709 上另外两条命中是 qwen-triage-finalize.yml 写的合法「Deferred approval withheld」状态评论,属我分类器的盲区,不是误命中。)
  • 0/120 个线程存在「marker 不在正文开头」的状态评论 ⇒ 改用 startswith 不会遗漏任何旧评论而产生重复。

迁移顺序也成立:合并后的树中 Post triage status comment 是第 5 步、Run Qwen Triage 是第 7 步,旧 marker 会在 agent 评估 duplicate guard 之前被改写 —— 与 PR 描述一致。

修正我自己在 R4 对 containsstartswith 的严重度判断

R4 时我基于「当前 0/30 误命中」的扫描,把它称为*「防御性加固,不是正在发生的 bug」*,并把 Stage 3 被破坏一事标为旁证。当前扫描结论没错,但「旁证」这个定性太弱了。运行 30163690366 已经能闭环:

第 5 步  Post triage status comment     15:29:11 → 15:29:12
第 7 步  Run Qwen Triage                15:29:12 → 15:34:01
第 9 步  Notify silent triage re-run    已跳过        ← 故排除 stage=rerun-summary
第 10 步 Finalize triage status comment 15:34:01 → 15:34:02
  • 5079059925(真正的状态评论)updated_at == created_at == 15:29:11finalize 确定从未碰过它
  • 5079075537 创建于 15:33:38(在 agent 步骤内),更新于 15:34:02(正是 finalize 运行的时刻)。finalize 不可能在自己启动前 24 秒创建一条评论。
  • 它现在的正文恰好是 301 字符的 finalize 模板(marker + 英文 + 中文,别无其他);PATCH 是整体替换。
  • 该时间窗内只有 triage job 能以机器人身份发 issue 评论:review-pr 直到 15:39:18 才启动,label / Classify PR / precheck 均在 15:29:02 前结束。
  • 本线程没有 stage=3 评论,15:29–15:34 之间也没有任何机器人 review(最早为 16:01:10,来自另一个 review workflow ⇒ 也不是终止门退出)。

⇒ finalize 覆盖了 agent 的 Stage 3 评论。该指纹在总量中出现 1/120(即本线程),而 68 个线程的状态评论都被正确原地更新。触发机制具有自指性:需要一条引用了该 marker 的机器人评论,而重命名该 marker 的这个 PR,正是 Stage 3 会讨论它的那个 PR。startswith 恰好封堵了这一点。这是本 PR 的加分项,不是问题。(仍然无法证明的部分:被破坏评论的具体内容 —— GitHub API 不提供评论编辑历史。)

发现 —— 2 个测试覆盖缺口 + 1 个小建议,无正确性阻塞项

9 个变异体针对两个测试文件(基线 36/36):

# 变异 结果
M1 post 步骤:删除 select(.user.login == $bot) ☠️ 被杀
M2 finalize 步骤:删除作者过滤 ☠️ 被杀
M3/M4 qwen-triage.ymlstartswithcontains(各步骤) ☠️ 被杀
M5 qwen-triage-finalize.ymlstartswithcontains ☠️ 被杀
M6 post 步骤:删除 BOT_LOGIN= 解析 ☠️ 被杀
M8 交换全部 6 处 MARKER/LEGACY_MARKER 赋值(完整语义回退) ☠️ 被杀(2 个测试)
M7 finalize 步骤:删除 BOT_LOGIN= 解析 🧟 存活 36/36
M9 qwen-triage.yml 两处查找中删除 or (.body | startswith($legacy)) 🧟 存活 36/36

F1 —— qwen-triage.yml 的旧 marker 迁移路径没有测试(中)。 M9 把两处查找中的 legacy 回退整体删掉,测试仍全绿。测试只断言 LEGACY_MARKER='…'定义,从未断言它被使用。而这正是本 PR 的核心行为(「旧评论会被复用并改写……不会产生重复状态评论」),并且承载着 71/120 个真实线程。一个能说明这是疏漏而非有意选择的不对称点:qwen-triage-finalize-workflow.test.js pin 了整条 jq 表达式,所以同类变异在那个文件里(M5)会被杀死。

F2 —— finalize 步骤的身份解析没有测试(低)。 M7 删掉 finalize 步骤的 BOT_LOGIN="$(gh api user …)" 后测试仍全绿,而 post 步骤的同样删除(M6)会被杀死 —— 因为 post 块断言了 gh api user --jq '.login',finalize 块没有。

三条断言即可封堵。我已应用并重跑:基线保持 36/36,M7 与 M9 双双被杀。

// statusStep 块中
expect(statusStep).toContain('startswith($legacy)');
// finalizeStep 块中
expect(finalizeStep).toContain('startswith($legacy)');
expect(finalizeStep).toContain("gh api user --jq '.login'");

F3 —— 身份解析失败时静默降级,与同族 workflow 不一致(小建议)。

BOT_LOGIN="$(gh api user --jq '.login' 2>/dev/null || true)"

一旦该调用失败,BOT_LOGIN 为空,select(.user.login == "") 匹配不到任何东西,两个步骤都会退化为「新建评论」。强制该调用失败后实测:post 步骤 → POST-NEW-COMMENT,finalize 步骤 → POST-NEW-COMMENT。这是故障安全的 —— 永不覆盖别人的评论,方向正确 —— 但 finalize 路径会让「🔄 running」评论永久滞留,同时再发一条,也就是我上文用来识别覆盖事故的那个双评论指纹。另外 2>/dev/null 会把原因一并吞掉。qwen-triage-finalize.yml:98 已有值得对齐的先例:

if ! BOT_LOGIN="$(gh api user --jq '.login')" || [ -z "$BOT_LOGIN" ]; then
  echo "::warning::Cannot resolve the bot identity; skipping (markers cannot be author-verified)."
  exit 0
fi

Token 身份本身是正确的:两个状态步骤都使用 GH_TOKEN: ${{ secrets.QWEN_CODE_BOT_TOKEN || secrets.CI_BOT_PAT }},与发评论的 token 相同,因此 gh api user 能解析出正确的 login。#7741#7709 上真实存在的「Deferred approval withheld」评论也证明该路径在生产环境已经工作正常。

不在本 PR 范围 —— 后续跟进,不构成阻塞

  • stage=rerun-summary 同属 stage= 命名空间;7/120 个线程带有该评论。触发概率低于当年的 stage=status(它只在重跑时发布,而 guard 只在无人值守事件上退出),但属于同一类问题。
  • .qwen/skills/triage/references/pr-workflow.md:32 仍写着 stage_comment_id() { # $1 = stage number (1, 2, 3) or "status"。属于死文本(唯一调用点是 stage_comment_id 2),但在本次重命名后已经过期。
  • 属于 main、与本 PR 无关: feat(triage): add sandboxed /verify deep-verification lane #7710/verify 通道使用冒号形式 marker —— <!‑‑ qwen-triage:verify ‑‑>:verify-state=running:verify-substantive —— 三者都逃过了 autofix 的 <!‑‑ (…|qwen-triage|…) 尾随空格过滤器。用 jq -n --arg b "$BODY" --arg f "$FILTER" '$b | test($f)' 验证:所有空格形式的 triage marker → true;三个冒号形式全部 → false。而对 qwen-code-ci-bot 的评论来说,该正则是唯一关卡(REVIEW_BOT 会直接通过 qwen-autofix.yml:2733 的 association 检查),因此 autofix 会把这些状态评论当成需要处理的评审反馈。与本 PR 已正确放弃的连字符形式属同一类 —— 建议单独开 PR 处理。

已执行的检查

  • head 上 qwen-triage-workflow + qwen-triage-finalize-workflow + qwen-autofix-workflow138/138
  • 试合并到当前 main7422bf879 —— 无冲突、无自动合并残留(已逐项确认合并后文件中的 marker 赋值、两处查找、BOT_LOGIN 解析均完整):181/181
  • 两个 workflow 上 actionlint -shellcheck=(与 CI 一致):merge-base 与 head exit 0。更严格的 shellcheck 集成报出的 3 个 SC2016:info,在 merge-base 上数量完全相同 —— 属既有问题,非本 PR 引入。
  • 两个提取出的状态脚本块上 bash -nshellcheck -s bash -S warning:干净。
  • 在合并后的树上重跑 marker 形态 oracle:<!‑‑ qwen-triage lifecycle ‑‑> → 被过滤 true(安全);连字符形式 → false。空格形式仍是正确选择,且完全不需要改动 qwen-autofix.yml

没有任何阻塞合并的问题。F1 是我希望能随本 PR 一起落地的一条 —— 只需一条断言,就能守住 71 个真实线程所依赖的行为。

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean fix that addresses every prior review finding; only non-blocking nit is the remaining stage=rerun-summary marker (acknowledged, deferred).

This PR has been through multiple review rounds and the current head incorporates all of them well. The lifecycle marker is the right call — it solves the duplicate-guard collision without creating an autofix filter regression. Legacy migration means threads still carrying the old marker self-heal on their next triage run instead of staying broken. Author filtering and startswith close the false-match surface that the earlier contains-without-author-filter left open.

The diff is minimal and every change serves the stated goal. Tests pin the exact patterns and include negative assertions that would catch regressions. CI is fully green on this commit. If I had to maintain this in six months, the code reads clearly and the test names explain the intent.

中文说明

置信度:4/5 —— 干净的修复,解决了此前所有审阅发现;唯一的非阻塞细节是剩余的 stage=rerun-summary marker(已说明,推迟处理)。

本 PR 经过多轮审阅,当前版本很好地吸收了所有反馈。lifecycle marker 是正确的选择——解决了 duplicate-guard 碰撞且不会造成 autofix 过滤器回退。Legacy 迁移使仍携带旧 marker 的线程在下次 triage 运行时自愈。作者过滤和 startswith 关闭了此前 contains 无作者过滤留下的误匹配面。

Diff 精简,每处改动都服务于既定目标。测试固定了精确模式并包含负向断言以捕获回归。CI 在此提交上全部通过。

Qwen Code · qwen3.8-max-preview

Reviewed at 7bb679415f1d3b7ca6eecba008bbc53e62b87927 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot has no review of its own on a567c05a4224479ef1699c1a0f3334bf5143b4b2. If this re-run was meant to approve, it did not — an approval left by another account is a separate vote and does not count as the bot's own.

The stage comments above were updated with the latest result. View workflow run.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Approve to Comment: CI still running. Reviewed.

中文说明

⚠️ 已从批准降级为评论:CI still running。 已审查。

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao
wenshao added this pull request to the merge queue Jul 26, 2026
Merged via the queue into QwenLM:main with commit e8e15e3 Jul 26, 2026
62 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants