2424
2525from greenlight import dispatch as dispatch_module
2626from greenlight import github_client , scan_runner , state
27- from greenlight .constants import DEFAULT_DISPATCH_REF , DEFAULT_TIMEOUT_MINUTES , TARGET_REPO , TERMINAL_STATUSES
27+ from greenlight .constants import (
28+ DEFAULT_DISPATCH_REF ,
29+ DEFAULT_TIMEOUT_MINUTES ,
30+ EXCLUDED_LABELS ,
31+ TARGET_REPO ,
32+ TERMINAL_STATUSES ,
33+ )
2834
2935if TYPE_CHECKING :
3036 from collections .abc import Callable , Sequence
@@ -102,15 +108,22 @@ def _close_client(client: Github) -> None:
102108
103109def _candidate_numbers (
104110 client : Github , * , pr : int | None , fetch : Callable [[Github ], list [OpenPR ]]
105- ) -> tuple [list [int ], dict [int , datetime | None ]]:
111+ ) -> tuple [list [int ], dict [int , datetime | None ], frozenset [ int ] ]:
106112 if pr is not None :
107113 logger .info ("targeting single PR #%d in %s" , pr , TARGET_REPO )
108- return [pr ], {}
114+ return [pr ], {}, frozenset ()
109115 open_prs = fetch (client )
110116 logger .info ("found %d open PR(s) from %d author(s) in %s" , len (open_prs ), len (TRUSTED_AUTHORS ), TARGET_REPO )
111117 for open_pr in open_prs :
112118 logger .info ("open PR #%d by %s: %s (%s)" , open_pr .number , open_pr .author , open_pr .title , open_pr .url )
113- return [open_pr .number for open_pr in open_prs ], {open_pr .number : open_pr .updated_at for open_pr in open_prs }
119+ stale_labeled_numbers = frozenset (
120+ open_pr .number for open_pr in open_prs if not EXCLUDED_LABELS .isdisjoint (open_pr .labels )
121+ )
122+ return (
123+ [open_pr .number for open_pr in open_prs ],
124+ {open_pr .number : open_pr .updated_at for open_pr in open_prs },
125+ stale_labeled_numbers ,
126+ )
114127
115128
116129def _within_recency_window (updated_at : datetime | None , now : datetime , window : timedelta ) -> bool :
@@ -124,28 +137,37 @@ def _recency_filter(
124137 pr_numbers : Sequence [int ],
125138 updated_at_by_number : dict [int , datetime | None ],
126139 states : dict [int , PRState ],
140+ stale_labeled_numbers : frozenset [int ],
127141 * ,
128142 now : datetime ,
129143 window : timedelta ,
130144) -> list [int ]:
131- """Drop stale PRs the scan can safely leave alone this iteration.
132-
133- A PR is kept when it was updated within ``window`` OR its recorded state is non-terminal
134- (in-flight or retry-eligible), so ``decide`` can still re-dispatch it on timeout/retry. A
135- stale PR is skipped without fingerprinting when it is terminal (its eval_hash cannot have
136- changed) or never reviewed (an untouched PR outside the window is not worth a first review).
145+ """Drop PRs the scan can safely leave alone this iteration.
146+
147+ A PR is kept when it was updated within ``window`` AND is not ``Stale``-labeled, OR its
148+ recorded state is non-terminal (in-flight or retry-eligible), so ``decide`` can still
149+ re-dispatch it on timeout/retry. A PR is skipped without fingerprinting when it is stale or
150+ ``Stale``-labeled and either terminal (its eval_hash cannot have changed) or never reviewed
151+ (an untouched PR is not worth a first review). The ``Stale`` label matters because the pytorch
152+ stale bot bumps ``updated_at`` when it applies the label, which would otherwise drag an
153+ abandoned never-reviewed PR back into the window.
137154 """
138155 kept : list [int ] = []
139156 for number in pr_numbers :
140- if _within_recency_window (updated_at_by_number .get (number ), now , window ):
157+ active = _within_recency_window (updated_at_by_number .get (number ), now , window )
158+ stale_labeled = number in stale_labeled_numbers
159+ if active and not stale_labeled :
141160 kept .append (number )
142161 continue
143162 recorded = states .get (number )
144- if recorded is None or recorded .status in TERMINAL_STATUSES :
145- detail = recorded .status if recorded is not None else "never reviewed"
146- logger .info ("skipping stale PR #%d: no recent activity (%s)" , number , detail )
163+ if recorded is not None and recorded .status not in TERMINAL_STATUSES :
164+ kept .append (number )
147165 continue
148- kept .append (number )
166+ detail = recorded .status if recorded is not None else "never reviewed"
167+ if stale_labeled :
168+ logger .info ("skipping PR #%d: Stale label (%s)" , number , detail )
169+ else :
170+ logger .info ("skipping stale PR #%d: no recent activity (%s)" , number , detail )
149171 return kept
150172
151173
@@ -199,7 +221,7 @@ def run(
199221 # exits non-zero, daemon backs off) rather than silently revert to hashing all human comments.
200222 authorized_logins = resolve_authorized ()
201223 logger .info ("filtering fingerprint comments to %d merge-authorized login(s)" , len (authorized_logins ))
202- pr_numbers , updated_at_by_number = _candidate_numbers (client , pr = pr , fetch = fetch )
224+ pr_numbers , updated_at_by_number , stale_labeled_numbers = _candidate_numbers (client , pr = pr , fetch = fetch )
203225 states = read_state (TARGET_REPO , pr_numbers )
204226 evaluated_at = now ()
205227 timeout = timedelta (minutes = timeout_minutes )
@@ -213,6 +235,7 @@ def run(
213235 pr_numbers ,
214236 updated_at_by_number ,
215237 states ,
238+ stale_labeled_numbers ,
216239 now = evaluated_at ,
217240 window = timedelta (hours = config .review_window_hours ),
218241 )
0 commit comments