fix(triage): never auto-approve cross-repo refactor PRs - #5744
Conversation
A fork (cross-repository) PR whose title is a `refactor` type could be auto-approved by the /triage skill and merged without a maintainer reviewing the structural changes (this happened with QwenLM#5089). Add a deterministic approval guardrail to Stage 3: before approving, check `isCrossRepository && title ~ /^refactor/i`; on a match, skip `gh pr review --approve` and escalate to the maintainer instead. Approval is now a positive condition (the guard must explicitly pass), so a blocked or empty check never approves. Document the rule in the skill's global Rules section as well.
|
Thanks for the PR, @wenshao! Template looks good ✓ — all required headings present, bilingual body, test plan table, risk section. On direction: this is a well-motivated safety net. The incident it references (#5089 — a fork refactor that got auto-approved and merged without human review) is exactly the kind of process gap that should be closed. The scope is narrow (just the triage skill prompt, no code changes), which keeps risk low. Aligned with the project's goal of having triage act as a responsible gatekeeper, not a rubber stamp. On approach: the diff is tight — 16 additions across 2 files, adding the rule in both the global Rules section (SKILL.md) and the Stage 3 approval flow (pr-workflow.md). The "positive condition" design (approve only when guard explicitly returns Moving on to code review. 🔍 中文说明感谢贡献,@wenshao! 模板完整 ✓ — 所有必需标题齐全,双语正文,测试计划表格,风险区。 方向:动机明确。引用的事件(#5089 — fork refactor 被自动 approve 并合入,未经人工审查)正是应当堵住的流程漏洞。改动范围窄(仅 triage skill prompt,无代码变更),风险低。与项目"triage 应负责任地把关,而非盲目盖章"的目标一致。 方案:diff 精简 — 2 个文件共 16 行新增,同时在全局 Rules 区(SKILL.md)和 Stage 3 审批流程(pr-workflow.md)中加了规则。"正向条件"设计(仅当护栏明确返回 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewThis is a clean, focused change. Two files, 16 additions, 2 deletions — all in the triage skill prompt. No code, no build system, no dependencies. The approach is sound: the guard lives in exactly the right place (right before the approve step in Stage 3), and the rule is also surfaced in the global Rules section for discoverability. The "positive condition" pattern — requiring One minor nit (already flagged in Stage 1): the regex No correctness bugs, no security concerns, no structural issues. Real-Scenario TestingN/A — this is a skill documentation (prompt) change with no user-visible or TUI surface. There is no binary behavior to test in tmux. Instead, verified the guard expression against the motivating incident (PR #5089) and synthetic boundary cases: All 6 boundary cases plus the real incident produce the expected result. 中文说明代码审查改动干净聚焦:2 个文件,16 行新增,2 行删除 — 全部在 triage skill prompt 中。无代码、无构建系统、无依赖。 方案合理:护栏放在 Stage 3 中最合适的位置(approve 步骤之前),规则同时在全局 Rules 区露出,便于发现。"正向条件"模式 — 要求 一个小瑕疵(Stage 1 已提到):正则 无正确性 bug、无安全隐患、无结构性问题。 真实场景测试N/A — 这是 skill 文档(prompt)改动,无用户可见 / TUI 界面。没有可以在 tmux 中测试的二进制行为变化。 改为对触发事件(PR #5089)和构造边界用例验证护栏表达式: — Qwen Code · qwen3.7-max |
|
Stepping back: this PR is what good open-source contribution looks like. A contributor hit a real process gap (fork refactor #5089 getting auto-approved), diagnosed the root cause precisely, and shipped the minimal fix. The diff is 16 lines across 2 markdown files — no code, no dependencies, no risk of breaking anything. The guard expression is correct and deterministic. I verified it against the real incident (PR #5089: correctly blocked) and 6 synthetic boundary cases (all pass). The "positive condition" design — requiring explicit The only reservation I have is the missing My independent proposal would have been slightly broader (also blocking Ships it. ✅ 中文说明退一步看:这是一个好的开源贡献的范例。贡献者遇到了真实的流程漏洞(fork refactor #5089 被自动 approve),精确诊断了根因,然后提交了最小修复。diff 只有 2 个 markdown 文件共 16 行 — 无代码、无依赖、无破坏风险。 护栏表达式正确且确定。我用真实事件(PR #5089:正确拦截)和 6 个构造边界用例(全部通过)做了验证。"正向条件"设计 — 要求明确返回 唯一的保留意见是正则缺少 我自己的独立方案会稍宽一些(也拦截 fork 的 可以合入。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
What this PR does
Adds an approval guardrail to the
/triageskill so it never auto-approves a cross-repository (fork) PR whose title is arefactortype. Right before the Stage 3 approve step, triage runs a deterministic check on the PR'sisCrossRepositoryflag and its title; when both match it skipsgh pr review --approveand escalates to the maintainer instead. Approval is now gated on a positive condition (the guard must explicitly returnok), so a blocked, failed, or empty check never results in an approval. The rule is also documented in the skill's global Rules section so it is visible at the skill entry point.Why it's needed
A fork
refactorPR (#5089,refactor(core): extract Protocol enum and decouple model identity from auth type) was auto-approved by triage and merged without a human maintainer reviewing the structural changes. Refactors touch code broadly and a fork author is not a trusted committer, so this class of PR should always get a human decision rather than an automated LGTM. This adds that safety net for future triage runs — both the GitHub Action (@qwen-code /triage, PR-opened auto-triage) and local/triage.Reviewer Test Plan
How to verify
The guard is the
jqexpression added to Stage 3 of the PR workflow. It returnsblockonly when the PR is cross-repository and the title starts withrefactor(case-insensitive). Run it against the motivating PR:Expected results for synthetic metadata:
refactor(core): xrefactor!: xRefactor the x(no colon)feat: refactor the xrefactor(core): xfix: a bugEvidence (Before & After)
N/A — this is a skill documentation (prompt) change with no user-visible / TUI surface.
Tested on
Verified the guard expression against PR #5089 and 9 boundary cases on macOS. Windows/Linux marked N/A: this is a markdown-only skill doc and the check is plain
gh+jq, which is platform-independent.Environment (optional)
Local
gh+jq. No app runtime involved.Risk & Scope
refactortitle prefix, so a refactor mislabeled asfeat:/chore:would not be caught. This is the known ceiling of a title-based, skill-level rule.Linked Issues
References #5089 as the motivating incident (no closing keyword — that PR is already merged).
中文说明
这个 PR 做了什么
给
/triageskill 加了一道 approve 护栏:对来自 fork(跨仓库)且标题是refactor类型的 PR,绝不自动 approve。在 Stage 3 执行 approve 之前,triage 会对 PR 的isCrossRepository标志和标题做一次确定性判定;两者同时命中时跳过gh pr review --approve,改为升级给维护者。现在 approve 是"正向条件"——护栏必须明确返回ok才批,因此判定被拦、失败或为空时都不会 approve。该规则同时写进 skill 的全局 Rules 区,确保在 skill 入口处就可见。为什么需要
一个 fork 的 refactor PR(#5089,
refactor(core): extract Protocol enum and decouple model identity from auth type)被 triage 自动 approve 并合入,期间没有维护者人工审查这些结构性改动。重构改动面广,而 fork 作者并非可信的提交者,所以这类 PR 应当始终交由人来决定,而不是自动 LGTM。本 PR 为未来的 triage(GitHub Action 的@qwen-code /triage、PR 打开时的自动 triage,以及本地/triage)补上这道保护。审查测试计划
如何验证
护栏就是 PR workflow Stage 3 中新增的那段
jq表达式:仅当 PR 来自跨仓库且标题以refactor开头(大小写不敏感)时返回block。对触发本次问题的 PR 运行:构造元数据的预期结果:
refactor(core): xrefactor!: xRefactor the x(无冒号)feat: refactor the xrefactor(core): xfix: a bug证据(改动前后)
N/A —— 这是 skill 文档(prompt)改动,无用户可见 / TUI 界面。
测试平台
仅在 macOS 上对 PR #5089 及 9 个边界用例验证了护栏表达式。Windows/Linux 标 N/A:纯 markdown 文档改动,判定只用
gh+jq,与平台无关。环境(可选)
本地
gh+jq,不涉及应用运行时。风险与范围
refactor标题前缀判定,因此被误标成feat:/chore:的重构不会被拦——这是"基于标题、skill 层规则"方案的固有上限。关联 Issue
引用 #5089 作为触发本次改动的事件(不带关闭关键字——该 PR 已合并)。