Fold "Close Stale PRs" into a deterministic pr-triage stale sweep#928
Conversation
Replace the agentic close-stale-prs.agent.md with a deterministic stale-PR sweep hosted in pr-triage-batch.yml. The new stale-sweep job runs .github/scripts/pr-stale-sweep.sh weekly (cron 17 4 * * 1) and on manual dispatch (stale_sweep=true), warning about and closing PRs with no non-bot activity for 30/37 days. Preserves the original policy (no-stale and maestro exemptions, drafts included, non-bot activity timer) without model calls or token cost. Deletes close-stale-prs.agent.md and its compiled lock file, and updates docs/design/pr-triage-workflows.md. Fixes #915 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Live validation (dry-run)I dispatched the new Run: https://github.com/dotnet/skills/actions/runs/29899244455 — What it decided (18 open PRs evaluated):
This exercises the warn threshold and the exemption/too-new filters. Per @JanKrivanek's guidance I did not post to any community PR, so this was dry-run only. No open PR currently exceeds the 37-day close threshold, so the close path had no live target today. Sample of a real stale closeThe close behavior this PR ports is the same one the (now-removed) agentic workflow performed. A recent real example is #244, auto-closed by
(Other historical stale closes by the old bot: #263, #610.) Notes
|
There was a problem hiding this comment.
Pull request overview
This PR replaces the agentic “close stale PRs” workflow with a deterministic stale-PR sweep integrated into the existing pr-triage-batch.yml orchestrator, reducing unreliability and eliminating model/token usage for a fully deterministic policy.
Changes:
- Added a deterministic stale-PR sweep script and a weekly/manual
stale-sweepjob inpr-triage-batch.yml. - Removed the legacy agentic stale-PR workflows (
close-stale-prs.agent.*). - Updated PR triage design docs to document the new stale-sweep policy and triggers.
Show a summary per file
| File | Description |
|---|---|
docs/design/pr-triage-workflows.md |
Documents the new stale-sweep job, its policy, and how no-stale is honored. |
.github/workflows/pr-triage-batch.yml |
Adds a weekly cron + dispatch option and a stale-sweep job to run the deterministic sweep. |
.github/workflows/close-stale-prs.agent.md |
Deleted legacy agentic stale PR workflow definition. |
.github/workflows/close-stale-prs.agent.lock.yml |
Deleted compiled lock workflow for the legacy agentic stale PR workflow. |
.github/scripts/pr-stale-sweep.sh |
New deterministic stale warning/close implementation using GitHub API via gh. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/5 changed files
- Comments generated: 2
|
👋 @JanKrivanek — this PR has 2 unresolved review thread(s). When you're ready, please address the feedback and push an update; the triage bot will pick up the next state automatically. (Add the |
Evangelink
left a comment
There was a problem hiding this comment.
Requesting changes because the destructive path currently fails open when activity reads fail, and the write cap can starve the stalest closures behind newer warnings. I reproduced both cases against this exact script. The existing 500-item enumeration-cap thread is also valid. I separately checked inline review replies on this repository: each reply produced a COMMENTED review with the same submitted_at, so that existing thread does not appear to be a blocker.
…, review comments Address code review on PR #928: - Never decide on partial data: if any activity source read fails, skip the PR instead of swallowing the error and risking a wrongful close. - Apply STALE_MAX only after sorting eligible PRs by inactivity (stalest first), so the cap can't starve an old closure behind a newer warning. - Enumerate open PRs with full pagination (gh api --paginate) instead of a fixed --limit. - Include inline review comments as a human-activity source. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Review round addressed (commit 9f81276)Reworked
Verification
|
Address Copilot re-review on PR #928: - Validate STALE_MAX/WARN_DAYS/CLOSE_DAYS are integers and CLOSE_DAYS > WARN_DAYS, so a bad manual dispatch fails fast with a clear message instead of an opaque bash arithmetic error. - Give the weekly stale sweep its own concurrency group so it and the hourly triage orchestrator don't block each other when their crons overlap (Mon 04:17). - Correct the design doc: the orchestrator posts a one-time idempotency marker comment when it dispatches the malicious-code scanner (it is not comment-free). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Re-review round addressed (commit 3a3c9b9)Three follow-up comments from the Copilot re-review are fixed, replied to, and resolved:
Verified: All review threads (7/7) are now resolved. @Evangelink — the two blocking items from your review were addressed in the prior round (9f81276); whenever you have a moment, a re-review to clear the changes-requested gate would be appreciated. Thanks! |
What & why
Fixes #915.
The agentic
close-stale-prs.agent.mdwas unreliable at actually closing stale PRs and spent tokens on work that is fully deterministic. This PR extracts and preserves that behavior as a deterministic stale-PR sweep folded into the existingpr-triageworkflow family, and deletes the agentic workflow.Changes
.github/scripts/pr-stale-sweep.sh— deterministic port of the stale policy (no model calls, no tokens):created_at.updated_atand all[bot]activity are ignored so the bot's own warning never resets the timer.<!-- pr-triage:stale-warning -->marker)no-stalelabel; authorsdotnet-maestro[bot]/dotnet-maestroDRY_RUNandSTALE_MAX(default 25) supported.github/workflows/pr-triage-batch.yml— hosts the newstale-sweepjob:17 4 * * 1(plus the existing hourly triage cron)workflow_dispatchinputsstale_sweep/stale_maxfor manual runspull-requests: write+issues: writepermissionsclose-stale-prs.agent.mdand its compiledclose-stale-prs.agent.lock.ymldocs/design/pr-triage-workflows.md(new "Stale-PR sweep" section, diagram,no-stalenote)Why fold into
pr-triage-batch.yml(not a new workflow)pr-triage-batch.ymlis the scheduled orchestrator and already exists onmainwithworkflow_dispatch, so the sweep can be dispatched against this feature branch before merge for validation. A brand-new workflow file cannot be dispatched until it lands on the default branch.Validation
actionlint -shellcheck= -pyflakes=passes onpr-triage-batch.yml.bash -npasses onpr-stale-sweep.sh.A live dry-run/real run against this repo and a sample closed PR will be linked in a follow-up comment.
Note
The
stale_sweep/stale_maxdispatch inputs are permanent (they're how the sweep is manually run), and they also serve as the trigger used to validate this PR from the branch.