From 9350ffcf4434be8a0d0cc83dc0df4ea0a5baa91b Mon Sep 17 00:00:00 2001 From: jeffrey701 Date: Tue, 19 May 2026 12:52:06 -0400 Subject: [PATCH] fix(issue-discovery): keep fresh scoring data when open-count fetch fails (#1249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run_issue_discovery makes two mirror calls per miner: one for the scoring payload (since=lookback) and one for the open-issue count. When the first succeeds and the second fails, the current code restores stale cached aggregates and drops the freshly-fetched solved-issue evidence — penalising miners for transient mirror flakiness on the supplementary call. Treat the open-count fetch as soft-fail: when it raises, fall back to the prior cycle's per-repo total_open_issues (via a new helper, _open_counts_from_cache) and continue scoring against the lookback dataset already in hand. Empty fallback when no cache row exists. Closes #1249 --- gittensor/validator/issue_discovery/scan.py | 41 ++++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/gittensor/validator/issue_discovery/scan.py b/gittensor/validator/issue_discovery/scan.py index c30a12e3..6564d236 100644 --- a/gittensor/validator/issue_discovery/scan.py +++ b/gittensor/validator/issue_discovery/scan.py @@ -198,12 +198,19 @@ async def run_issue_discovery( try: current_response = await asyncio.to_thread(client.get_miner_issues, evaluation.github_id) except MirrorRequestError as e: - bt.logging.warning(f'├─ UID {uid}: open-issue count fetch failed ({e}) — skipped this miner') - _restore_issue_discovery_from_cache(evaluation, evaluation_cache) - fetch_errors += 1 - continue - - open_counts = _count_open_issues(current_response.issues, enabled_names) + # Soft-fail: the open-count call is supplementary to the scoring + # payload already fetched above. Discarding fresh solved-issue + # evidence because of a transient open-count failure penalises + # miners for mirror flakiness. Fall back to last cycle's per-repo + # open counts when available; otherwise zero. Scoring proceeds. + open_counts = _open_counts_from_cache(evaluation, evaluation_cache, enabled_names) + bt.logging.warning( + f'├─ UID {uid}: open-issue count fetch failed ({e}) — ' + f'using cached open counts ({sum(open_counts.values())} total); ' + f'scoring proceeds with fresh lookback data' + ) + else: + open_counts = _count_open_issues(current_response.issues, enabled_names) filtered = [i for i in response.issues if i.repo_full_name in enabled_names and _should_include_issue(i)] if not filtered: _clear_issue_discovery_fields(evaluation) @@ -362,6 +369,28 @@ def _count_open_issues(issues: List[MirrorIssue], enabled_names: Set[str]) -> Di return counts +def _open_counts_from_cache( + evaluation: MinerEvaluation, + evaluation_cache: Optional[MinerEvaluationCache], + enabled_names: Set[str], +) -> Dict[str, int]: + """Recover per-repo open-issue counts from the prior cycle's cached + evaluation. Used as the soft-fail fallback when the open-count fetch + fails but the scoring fetch succeeded, so fresh solved-issue evidence + isn't discarded. Returns {} when no cache row is available — scoring + then runs with zero open counts rather than dropping the miner.""" + if evaluation_cache is None: + return {} + cached = evaluation_cache.get(evaluation.uid, evaluation.hotkey, evaluation.github_id or '') + if cached is None: + return {} + counts: Dict[str, int] = {} + for repo_name, repo_eval in cached.repo_evaluations.items(): + if repo_name in enabled_names and repo_eval.total_open_issues: + counts[repo_name] = repo_eval.total_open_issues + return counts + + def _build_solving_pr_cache( miner_evaluations: Dict[int, MinerEvaluation], ) -> Dict[Tuple[str, int], CachedSolvingPR]: