Skip to content

Commit 66ab838

Browse files
authored
Merge pull request #22 from coygeek/droid/issue-17-required-check-identity
Fix duplicate required check identity handling
2 parents 8858aab + 457fd0a commit 66ab838

2 files changed

Lines changed: 74 additions & 8 deletions

File tree

‎src/agent_merge_queue/cli.py‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,15 @@ def normalize_check_state(check: dict[str, Any]) -> str:
148148
return "UNKNOWN"
149149

150150

151+
def check_identity(check: dict[str, Any], name: str) -> tuple[str, str]:
152+
typename = str(check.get("__typename") or "")
153+
if not typename:
154+
typename = "StatusContext" if check.get("context") else "CheckRun"
155+
return (typename, name)
156+
157+
151158
def check_states(checks: Iterable[dict[str, Any]]) -> dict[str, str]:
152-
grouped: dict[str, tuple[str, int, str]] = {}
159+
grouped: dict[tuple[str, tuple[str, str]], tuple[str, int, str]] = {}
153160
for index, check in enumerate(checks):
154161
name = str(check.get("name") or check.get("context") or "")
155162
if not name:
@@ -168,17 +175,23 @@ def check_states(checks: Iterable[dict[str, Any]]) -> dict[str, str]:
168175
state = normalize_check_state(check)
169176
# A newly queued run may not have a timestamp yet. Fail closed instead
170177
# of letting an older success hide that pending rerun.
171-
order = "\uffff" if not timestamp and state not in PASSED_CHECK_STATES else timestamp
178+
order = (
179+
"\uffff" if not timestamp and state not in PASSED_CHECK_STATES else timestamp
180+
)
172181
candidate = (order, index, state)
173-
if name not in grouped or candidate[:2] > grouped[name][:2]:
174-
grouped[name] = candidate
182+
key = (name, check_identity(check, name))
183+
if key not in grouped or candidate[:2] > grouped[key][:2]:
184+
grouped[key] = candidate
185+
186+
states_by_name: dict[str, list[str]] = {}
187+
for (name, _identity), value in grouped.items():
188+
states_by_name.setdefault(name, []).append(value[2])
175189

176190
result: dict[str, str] = {}
177-
for name, value in grouped.items():
178-
state = value[2]
179-
if state in FAILED_CHECK_STATES:
191+
for name, states in states_by_name.items():
192+
if any(state in FAILED_CHECK_STATES for state in states):
180193
result[name] = "failed"
181-
elif state in PASSED_CHECK_STATES:
194+
elif all(state in PASSED_CHECK_STATES for state in states):
182195
result[name] = "passed"
183196
else:
184197
result[name] = "pending"

‎tests/test_cli.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,59 @@ def test_undated_queued_rerun_hides_older_success(self) -> None:
16551655

16561656
self.assertEqual(states["CI"], "pending")
16571657

1658+
def test_same_name_check_run_and_failed_status_blocks_required_check(self) -> None:
1659+
states = check_states(
1660+
[
1661+
{"__typename": "CheckRun", "name": "CI", "conclusion": "SUCCESS"},
1662+
{"__typename": "StatusContext", "context": "CI", "state": "FAILURE"},
1663+
]
1664+
)
1665+
self.assertEqual(states["CI"], "failed")
1666+
1667+
value = entry(1)
1668+
value.checks = states
1669+
value.classify(CONFIG)
1670+
1671+
self.assertEqual(value.state, "blocked")
1672+
self.assertIn("CI failed", value.reasons)
1673+
1674+
def test_same_name_check_run_and_pending_status_waits_required_check(self) -> None:
1675+
states = check_states(
1676+
[
1677+
{"__typename": "CheckRun", "name": "CI", "conclusion": "SUCCESS"},
1678+
{"__typename": "StatusContext", "context": "CI", "state": "PENDING"},
1679+
]
1680+
)
1681+
self.assertEqual(states["CI"], "pending")
1682+
1683+
value = entry(1)
1684+
value.checks = states
1685+
value.classify(CONFIG)
1686+
1687+
self.assertEqual(value.state, "waiting")
1688+
self.assertIn("CI is not complete", value.reasons)
1689+
1690+
def test_exact_check_run_replaces_stale_rollup_check_run_identity(self) -> None:
1691+
states = check_states(
1692+
[
1693+
{
1694+
"__typename": "CheckRun",
1695+
"name": "CI",
1696+
"status": "QUEUED",
1697+
"workflowName": "Build",
1698+
"startedAt": "2026-06-20T00:00:00Z",
1699+
},
1700+
{
1701+
"name": "CI",
1702+
"conclusion": "SUCCESS",
1703+
"app": {"slug": "github-actions"},
1704+
"started_at": "2026-06-20T00:01:00Z",
1705+
},
1706+
]
1707+
)
1708+
1709+
self.assertEqual(states["CI"], "passed")
1710+
16581711
def test_review_verdicts_are_classified_generically(self) -> None:
16591712
value = entry(1)
16601713
value.review_verdicts = (ReviewVerdict("Any bot", "blocked", ("one finding",)),)

0 commit comments

Comments
 (0)