Skip to content

ci(stale): enable 60+30 stale/close policy for pull requests#3375

Merged
wenshao merged 5 commits into
QwenLM:mainfrom
wenshao:feat/stale-pr-28-28
Apr 19, 2026
Merged

ci(stale): enable 60+30 stale/close policy for pull requests#3375
wenshao merged 5 commits into
QwenLM:mainfrom
wenshao:feat/stale-pr-28-28

Conversation

@wenshao

@wenshao wenshao commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

Background

qwen-code currently has 139 open PRs, 36 of which have had no activity for more than 60 days. One direct reason: the pre-existing if guard in .github/workflows/stale.yml was hard-coded to google-gemini/gemini-cli, so the workflow never executed in QwenLM/qwen-code — the stale automation has in fact never been active.

What this PR does

  • Drop the obsolete if guard entirely. Scheduled runs are already disabled on forks by GitHub, and workflow_dispatch is manually-triggered, so the guard added no real safety — only maintenance overhead.
  • Scope this change to pull requests only; issues are deliberately left untouched for now (see "Why issues are deferred" below).
  • When a PR has no activity for 60 days → apply the status/stale label and post a reminder comment.
  • After another 30 days with still no activity → auto-close the PR.
  • Exempt labels: pinned, security, status/blocked, status/on-hold, status/ready-for-merge.
  • remove-stale-when-updated: true — any push by the author or any comment automatically strips the stale label on the next cron run.
  • ascending: true + operations-per-run: 100 — older PRs are processed first, and each run is capped at 100 operations to stay well under GitHub's hourly rate limit.

Why 60 + 30

Calibrated against common industry practice:

Project stale close total window
OpenJDK (Skara) 28 days 28 days 56 days
Kubernetes 90 days 30 days 120 days
qwen-code (this PR) 60 days 30 days 90 days

Reasons for choosing 60 + 30:

  • A longer pre-stale window (60 vs. 35 days) reduces false positives while review SLA is still unstable — authors and reviewers get a full two-month buffer before the first nag.
  • A shorter post-stale grace (30 vs. 35 days) matches the Kubernetes pattern: once the stale signal has landed, 30 days of silence is a strong indicator that the PR is truly dead.
  • The 90-day total sits comfortably between OpenJDK and Kubernetes.

These numbers are not final. I recommend revisiting the false-close rate after 2–3 months of operation and adjusting the windows once the backlog stabilizes.

Why issues are deferred

  • The vast majority of open issues have not been triaged; opening auto-close on them today would close valid bug reports.
  • The safer sequence is to first build out the triage label taxonomy (type/bug-confirmed etc.), then introduce an issue-level stale policy in a separate PR.

Expected first-run impact (based on the current 139 open PRs)

Bucket Count
Silent for 60–89 days → will be marked stale + commented on this run 6
Silent for ≥ 90 days → will be marked stale on this run, closed 30 days later 30
Already carries an exempt label 0
Fresh (< 60 days) 103

Nothing closes on day one — every affected PR gets a full 30-day reaction window. Authors can push or comment to keep it alive, and maintainers can apply an exempt label to block auto-close.

Trigger mechanism

  • Daily cron at UTC 00:30 (Beijing time 08:30).
  • Can also be triggered manually via workflow_dispatch in the Actions tab.
  • Worst-case latency: 24 hours (the next cron window).

Follow-ups (after merge)

  1. Scan every workflow for the same google-gemini/gemini-cli guard bug (at minimum gemini-self-assign-issue.yml has this problem).
  2. Encourage reviewers to apply status/ready-for-merge after approving, so PRs waiting only on a merge are not mis-closed.
  3. After 2–3 months of operation, measure the false-close rate and the rebound rate, then adjust the windows based on data.
  4. Once triage labels are ready, open a separate PR introducing the issue-level stale policy.

Test plan

  • After merge, trigger workflow_dispatch once manually to validate the ascending order, exempt-label handling, and the operations-per-run: 100 batching.
  • Verify that a PR carrying status/ready-for-merge is skipped.

背景

qwen-code 当前有 139 个 open PR,其中 36 个已经超过 60 天没有活动。一个直接原因:原先 .github/workflows/stale.ymlif 守卫写死的是 google-gemini/gemini-cli,在 QwenLM/qwen-code 仓库里永远不会执行,也就是 stale 自动化从来没生效过。

这个 PR 做了什么

  • 彻底去掉过时的 if 守卫。GitHub 对 fork 默认禁用 scheduled workflow,workflow_dispatch 本身又只有写权限者能触发,所以这层守卫没有实际安全收益,只增加维护负担。
  • 本次只处理 PR,issue 暂时保留(见下方「为什么 issue 先不动」)。
  • PR 连续 60 天无活动 → 打 status/stale 标签 + 发提醒评论。
  • 再过 30 天仍无活动 → 自动关闭。
  • 豁免标签:pinnedsecuritystatus/blockedstatus/on-holdstatus/ready-for-merge
  • remove-stale-when-updated: true——作者 push 或任何人评论后,下一次 cron 自动撕掉 stale 标签。
  • ascending: true + operations-per-run: 100——老 PR 优先处理,每次最多操作 100 条,远离 GitHub 小时级限流红线。

为什么是 60 + 30

对标业界常见做法:

项目 stale close 总窗口
OpenJDK (Skara) 28 天 28 天 56 天
Kubernetes 90 天 30 天 120 天
qwen-code (本 PR) 60 天 30 天 90 天

选 60 + 30 的理由:

  • 更长的 pre-stale 窗口(60 vs. 35 天)在 review SLA 还不稳定时能减少误判——作者和 reviewer 在首次提醒前有完整的两个月缓冲。
  • 更短的 post-stale 宽限(30 vs. 35 天)沿用 Kubernetes 的做法:既然 stale 信号已经到位,再 30 天无响应基本可以判定 PR 已死。
  • 90 天总窗口正好介于 OpenJDK 和 Kubernetes 之间。

数字不是最终的。建议运行 2–3 个月后复盘误关率,再根据数据调整。

为什么 issue 先不动

  • 当前 open issue 绝大多数没被分诊,直接开 auto-close 会误关有效 bug 报告。
  • 更合理的顺序是先完善 type/bug-confirmed 等分诊标签体系,再用单独 PR 处理 issue 的 stale 策略。

首次运行预估影响(基于当前 139 个 open PR)

分类 数量
已静默 60–89 天 → 本次打 stale 标签 + 评论 6
已静默 ≥ 90 天 → 本次打 stale 标签,30 天后关闭 30
带豁免标签 0
Fresh(< 60 天) 103

第一天不会关任何 PR——每个受影响的 PR 都有完整的 30 天反应窗口,作者可以 push / 评论续命,maintainer 也可以打豁免标签拦住。

触发机制

  • 每天 UTC 00:30(北京时间 08:30)cron 触发。
  • 也可以在 Actions 页面手动 workflow_dispatch
  • 最坏情况延迟 24 小时(下一个 cron 窗口)。

合并后的 Follow-ups

  1. 扫一遍所有 workflow,找出其他写死 google-gemini/gemini-cli 的坏守卫(至少 gemini-self-assign-issue.yml 有同样问题)。
  2. 推动 reviewer 在 approve 之后打 status/ready-for-merge 豁免标签,避免长期 approve 但卡在等合并的 PR 被误关。
  3. 运行 2–3 个月后统计误关率和反弹率,根据数据调整窗口。
  4. 等分诊标签完善后,单独开 PR 引入 issue 的 stale policy。

Test plan

  • 合并后手动 workflow_dispatch 一次,验证 ascending 顺序、豁免标签、operations-per-run: 100 的批量行为。
  • 验证带 status/ready-for-merge 的 PR 被豁免不处理。

- Fix the repository guard so the workflow actually runs on
  QwenLM/qwen-code (it was previously gated to google-gemini/gemini-cli
  and never executed in this repo).
- Scope the behavior to pull requests for now; issue policy will be
  introduced separately once triage labels are in place.
- Mark a PR stale after 4 weeks without activity, then close it after
  another 4 weeks.
- Exempt pinned, security, status/blocked, status/on-hold, and
  status/ready-for-merge from auto-close.
- Remove the stale label automatically when activity resumes, and
  process the oldest PRs first on each run.
@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR enables the stale bot for pull requests in the QwenLM/qwen-code repository with a 28+28 day policy (4 weeks to stale, 4 weeks to close). The implementation is well-structured, clearly documented, and follows a measured approach to backlog management that matches established patterns (OpenJDK).

🔍 General Feedback

  • Excellent documentation: The PR body provides clear summary, expected impact analysis with concrete numbers, and a thorough test plan.
  • Conservative approach: Intentionally excluding issues until triage labels are in place shows good judgment.
  • Proper exemptions: The exempt labels (pinned, security, status/blocked, status/on-hold, status/ready-for-merge) cover the right scenarios.
  • Good defaults: remove-stale-when-updated: true and ascending: true are sensible choices for fair PR handling.

🎯 Specific Feedback

🟢 Medium

  • .github/workflows/stale.yml:13 - The repository guard condition change from google-gemini/gemini-cli to QwenLM/qwen-code is correct, but consider whether this workflow should be gated at all if this is the canonical repository now. If the workflow is only intended for QwenLM/qwen-code, the guard could potentially be removed entirely to simplify the configuration.

🔵 Low

  • .github/workflows/stale.yml:27 - The stale-pr-label: 'status/stale' is set but not included in the exempt-pr-labels list. This is likely intentional (stale PRs should still close), but worth confirming this is the desired behavior.

  • .github/workflows/stale.yml:38 - Consider adding a comment explaining why operations-per-run: 100 was chosen. This seems reasonable for the current backlog (~157 PRs), but documenting the reasoning would help future maintainers.

✅ Highlights

  • Well-researched policy: The 28+28 day cadence matching OpenJDK is a proven approach.
  • Clear communication: The stale and close messages are friendly and informative, explaining exactly what authors need to do.
  • Thoughtful rollout plan: The test plan with workflow_dispatch before and after merge shows good operational discipline.
  • Proper pinning: The actions/stale action uses a pinned commit SHA (5bef64f19d7facfb25b37b414482c7164d639639) with a ratchet comment, which is excellent security practice.

Five weeks + five weeks gives contributors more slack around holidays
and busy periods, and reduces the first-run impact on the existing
backlog. The total window moves from 56 days to 70 days.
@wenshao wenshao changed the title ci(stale): enable 28+28 stale/close policy for pull requests ci(stale): enable 35+35 stale/close policy for pull requests Apr 16, 2026
Shift by one hour so results are ready before the Beijing work day
starts (08:30 local), while still avoiding the top of the hour (the
high-contention window for GitHub-hosted runners) and staying 30
minutes after release.yml at 00:00 UTC.
@pomelo-nwu

Copy link
Copy Markdown
Collaborator

LGTM — the changes are clean and correct: guard fix, 35+35 PR stale policy, and exempt labels are all reasonable. Two non-blocking suggestions:

  1. The if guard could potentially be removed — if this workflow is only intended to run in the canonical repo, the guard adds maintenance overhead (forks not running it is fine, and workflow_dispatch is already controllable).
  2. operations-per-run: 100 could benefit from a brief comment explaining the rationale, to help future maintainers.

After merge, remember to manually trigger workflow_dispatch once to verify, and follow up on the same guard issue in gemini-self-assign-issue.yml.


LGTM — 改动干净正确:守卫修复、35+35 策略、豁免标签都合理。两个非阻塞建议:

  1. if guard 可以考虑去掉——如果这个 workflow 只会在 canonical repo 运行,guard 反而增加维护成本(fork 跑不跑无所谓,workflow_dispatch 本身就可控)。
  2. operations-per-run: 100 加一行注释说明选择理由,方便后续维护者理解。

合并后记得手动 workflow_dispatch 触发一次验证,并跟进 gemini-self-assign-issue.yml 的同类守卫问题。

pomelo-nwu
pomelo-nwu previously approved these changes Apr 17, 2026
- Remove the `github.repository == 'QwenLM/qwen-code'` job guard:
  scheduled runs are already disabled on forks by GitHub, and
  workflow_dispatch is manually-triggered so the guard adds no safety.
- Add a comment explaining the `operations-per-run: 100` rationale
  (rate-limit headroom given the ~150-PR backlog).
@wenshao wenshao enabled auto-merge (squash) April 17, 2026 04:19
@wenshao wenshao removed the request for review from tanzhenxin April 17, 2026 04:19
@wenshao wenshao disabled auto-merge April 17, 2026 04:19
@wenshao wenshao enabled auto-merge (squash) April 17, 2026 04:19
@wenshao wenshao requested a review from pomelo-nwu April 17, 2026 04:25
Give authors a longer idle window before the stale warning (60 days)
while shortening the grace period after the warning (30 days). Total
time-to-close is unchanged at 90 days, but the louder signal now lands
closer to the actual closure so authors are less likely to be caught
off guard by a silent auto-close.
@wenshao wenshao changed the title ci(stale): enable 35+35 stale/close policy for pull requests ci(stale): enable 60+30 stale/close policy for pull requests Apr 18, 2026
@wenshao wenshao merged commit f340d95 into QwenLM:main Apr 19, 2026
13 checks passed
TaimoorSiddiquiOfficial pushed a commit to TaimoorSiddiquiOfficial/HopCode that referenced this pull request Apr 19, 2026
Merged 6 upstream commits while preserving HopCode architecture:

Features synced from upstream:
- feat(mcp): OSC 52 copy hotkey for OAuth authorization URL (QwenLM#3393)
  Press 'c' during OAuth to copy URL via terminal clipboard, works over SSH
- feat(cli): early input capture to prevent keystroke loss during startup (QwenLM#3319)
  Buffers keystrokes during REPL init, replays once UI is mounted
- perf(vscode): fix input lag in long conversations via React.memo (QwenLM#2550)
  MessageList/UserMessage/AssistantMessage wrapped with React.memo
- feat(vscode-ide-companion): agent execution tool display (QwenLM#2590)
  Render dedicated agent execution cards in webview
- fix(build): invoke tsx via node --import instead of npx (QwenLM#3237)
  Fixes bun compatibility for generate:settings-schema script
- ci(stale): enable 35+35 stale/close PR policy (QwenLM#3375)

Conflict resolution:
- packages/vscode-ide-companion/.../toolcalls/index.tsx: kept @hoptrendy/webui,
  added ToolCallData to imports (upstream added it)
- All @qwen-code/* import paths preserved as @hoptrendy/* (HopCode arch)
- HopCode branding, version, and CI workflows preserved

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
…3375)

* ci(stale): enable 28+28 stale/close policy for pull requests

- Fix the repository guard so the workflow actually runs on
  QwenLM/qwen-code (it was previously gated to google-gemini/gemini-cli
  and never executed in this repo).
- Scope the behavior to pull requests for now; issue policy will be
  introduced separately once triage labels are in place.
- Mark a PR stale after 4 weeks without activity, then close it after
  another 4 weeks.
- Exempt pinned, security, status/blocked, status/on-hold, and
  status/ready-for-merge from auto-close.
- Remove the stale label automatically when activity resumes, and
  process the oldest PRs first on each run.

* ci(stale): loosen PR cadence from 28+28 to 35+35

Five weeks + five weeks gives contributors more slack around holidays
and busy periods, and reduces the first-run impact on the existing
backlog. The total window moves from 56 days to 70 days.

* ci(stale): move cron from 01:30 UTC to 00:30 UTC

Shift by one hour so results are ready before the Beijing work day
starts (08:30 local), while still avoiding the top of the hour (the
high-contention window for GitHub-hosted runners) and staying 30
minutes after release.yml at 00:00 UTC.

* ci(stale): drop redundant repo guard and document ops-per-run

- Remove the `github.repository == 'QwenLM/qwen-code'` job guard:
  scheduled runs are already disabled on forks by GitHub, and
  workflow_dispatch is manually-triggered so the guard adds no safety.
- Add a comment explaining the `operations-per-run: 100` rationale
  (rate-limit headroom given the ~150-PR backlog).
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.

2 participants