@@ -72,11 +72,17 @@ def get_check_runs(self, repo: str, head_sha: str) -> dict:
7272 return response
7373
7474 def get_own_check_run_id (self , repo : str , head_sha : str , check_name : str ) -> int :
75- """Return a fixed check run ID by scanning check_runs_responses ."""
75+ """Return a fixed check run ID, preferring the in-progress instance ."""
7676 if self ._check_runs_responses :
77+ fallback : int | None = None
7778 for cr in self ._check_runs_responses [0 ].get ("check_runs" , []):
7879 if cr ["name" ] == check_name :
79- return cr ["id" ]
80+ if cr ["status" ] == "in_progress" :
81+ return cr ["id" ]
82+ if fallback is None :
83+ fallback = cr ["id" ]
84+ if fallback is not None :
85+ return fallback
8086 raise ChecksError (f"Check run '{ check_name } ' not found" )
8187
8288
@@ -389,6 +395,69 @@ def test_empty_check_runs_retries_then_fails(self):
389395 wait_for_checks (gh , "owner/repo" , "abc123" , check_run_id = 999 , delay = 0 , retries = 2 , interval = 5 )
390396 assert gh .get_check_runs .call_count == 2
391397
398+ def test_stale_completed_run_with_same_name_excluded_by_ignore (self ):
399+ """A stale completed check_ci_status run is excluded via ignore_checks."""
400+ gh = MagicMock (spec = GhClient )
401+ gh .get_check_runs .return_value = json .loads (
402+ _api_response (
403+ _make_check_run (800 , "check_ci_status" , "completed" , "failure" ),
404+ _make_check_run (999 , "check_ci_status" , "in_progress" ),
405+ _make_check_run (100 , "lint" , "completed" , "success" ),
406+ )
407+ )
408+ wait_for_checks (
409+ gh ,
410+ "owner/repo" ,
411+ "abc123" ,
412+ check_run_id = 999 ,
413+ delay = 0 ,
414+ retries = 1 ,
415+ interval = 0 ,
416+ ignore_checks = frozenset ({"check_ci_status" }),
417+ )
418+
419+ def test_stale_failed_run_causes_false_failure_without_ignore (self ):
420+ """Without ignore_checks, a stale failed run with the same name causes failure."""
421+ gh = MagicMock (spec = GhClient )
422+ gh .get_check_runs .return_value = json .loads (
423+ _api_response (
424+ _make_check_run (800 , "check_ci_status" , "completed" , "failure" ),
425+ _make_check_run (999 , "check_ci_status" , "in_progress" ),
426+ _make_check_run (100 , "lint" , "completed" , "success" ),
427+ )
428+ )
429+ with pytest .raises (ChecksError , match = "check_ci_status" ):
430+ wait_for_checks (
431+ gh ,
432+ "owner/repo" ,
433+ "abc123" ,
434+ check_run_id = 999 ,
435+ delay = 0 ,
436+ retries = 1 ,
437+ interval = 0 ,
438+ )
439+
440+ def test_concurrent_in_progress_run_excluded_by_ignore (self ):
441+ """Two concurrent in-progress runs with same name don't deadlock when using ignore_checks."""
442+ gh = MagicMock (spec = GhClient )
443+ gh .get_check_runs .return_value = json .loads (
444+ _api_response (
445+ _make_check_run (998 , "check_ci_status" , "in_progress" ),
446+ _make_check_run (999 , "check_ci_status" , "in_progress" ),
447+ _make_check_run (100 , "lint" , "completed" , "success" ),
448+ )
449+ )
450+ wait_for_checks (
451+ gh ,
452+ "owner/repo" ,
453+ "abc123" ,
454+ check_run_id = 999 ,
455+ delay = 0 ,
456+ retries = 1 ,
457+ interval = 0 ,
458+ ignore_checks = frozenset ({"check_ci_status" }),
459+ )
460+
392461 def test_many_check_runs_all_evaluated (self ):
393462 """All check runs are evaluated, not just the first page worth."""
394463 gh = MagicMock (spec = GhClient )
@@ -446,14 +515,25 @@ def test_get_own_check_run_id_finds_matching_check(self, mock_run):
446515 assert client .get_own_check_run_id ("owner/repo" , "abc123" , "check_ci_status" ) == 200
447516
448517 @patch ("ci_checks.ci_checks.subprocess.run" )
449- def test_get_own_check_run_id_returns_first_match (self , mock_run ):
450- """get_own_check_run_id returns the first matching ID when duplicates exist ."""
518+ def test_get_own_check_run_id_prefers_in_progress (self , mock_run ):
519+ """get_own_check_run_id prefers the in-progress run over a completed one ."""
451520 response = _api_response (
452521 _make_check_run (200 , "check_ci_status" , "completed" , "success" ),
453522 _make_check_run (300 , "check_ci_status" , "in_progress" ),
454523 )
455524 mock_run .return_value = subprocess .CompletedProcess (args = [], returncode = 0 , stdout = response )
456525 client = GhClient ()
526+ assert client .get_own_check_run_id ("owner/repo" , "abc123" , "check_ci_status" ) == 300
527+
528+ @patch ("ci_checks.ci_checks.subprocess.run" )
529+ def test_get_own_check_run_id_falls_back_to_completed (self , mock_run ):
530+ """get_own_check_run_id falls back to completed run when none is in-progress."""
531+ response = _api_response (
532+ _make_check_run (200 , "check_ci_status" , "completed" , "success" ),
533+ _make_check_run (300 , "check_ci_status" , "completed" , "success" ),
534+ )
535+ mock_run .return_value = subprocess .CompletedProcess (args = [], returncode = 0 , stdout = response )
536+ client = GhClient ()
457537 assert client .get_own_check_run_id ("owner/repo" , "abc123" , "check_ci_status" ) == 200
458538
459539 @patch ("ci_checks.ci_checks.subprocess.run" )
0 commit comments