Skip to content

Commit 91d0354

Browse files
fix: preserve duplicate required check identities
Aggregate same-name check runs and commit statuses fail-closed after reducing each identity to its latest state so DeployBot does not mark a required context passed while another matching entry is failed or pending. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent d5def48 commit 91d0354

2 files changed

Lines changed: 58 additions & 7 deletions

File tree

‎src/agent_merge_queue/cli.py‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,23 @@ def normalize_check_state(check: dict[str, Any]) -> str:
129129
return "UNKNOWN"
130130

131131

132+
def check_identity(check: dict[str, Any], name: str) -> tuple[str, str]:
133+
typename = str(check.get("__typename") or "")
134+
if not typename:
135+
typename = "StatusContext" if check.get("context") else "CheckRun"
136+
app = check.get("app")
137+
app_slug = app.get("slug") if isinstance(app, dict) else ""
138+
source = str(
139+
app_slug
140+
or check.get("workflowName")
141+
or check.get("context")
142+
or name
143+
)
144+
return (typename, source)
145+
146+
132147
def check_states(checks: Iterable[dict[str, Any]]) -> dict[str, str]:
133-
grouped: dict[str, tuple[str, int, str]] = {}
148+
grouped: dict[tuple[str, tuple[str, str]], tuple[str, int, str]] = {}
134149
for index, check in enumerate(checks):
135150
name = str(check.get("name") or check.get("context") or "")
136151
if not name:
@@ -148,15 +163,19 @@ def check_states(checks: Iterable[dict[str, Any]]) -> dict[str, str]:
148163
# of letting an older success hide that pending rerun.
149164
order = "\uffff" if not timestamp and state != "SUCCESS" else timestamp
150165
candidate = (order, index, state)
151-
if name not in grouped or candidate[:2] > grouped[name][:2]:
152-
grouped[name] = candidate
166+
key = (name, check_identity(check, name))
167+
if key not in grouped or candidate[:2] > grouped[key][:2]:
168+
grouped[key] = candidate
169+
170+
states_by_name: dict[str, list[str]] = {}
171+
for (name, _identity), value in grouped.items():
172+
states_by_name.setdefault(name, []).append(value[2])
153173

154174
result: dict[str, str] = {}
155-
for name, value in grouped.items():
156-
state = value[2]
157-
if state in FAILED_CHECK_STATES:
175+
for name, states in states_by_name.items():
176+
if any(state in FAILED_CHECK_STATES for state in states):
158177
result[name] = "failed"
159-
elif state == "SUCCESS":
178+
elif all(state == "SUCCESS" for state in states):
160179
result[name] = "passed"
161180
else:
162181
result[name] = "pending"

‎tests/test_cli.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,38 @@ def test_undated_queued_rerun_hides_older_success(self) -> None:
197197

198198
self.assertEqual(states["CI"], "pending")
199199

200+
def test_same_name_check_run_and_failed_status_blocks_required_check(self) -> None:
201+
states = check_states(
202+
[
203+
{"__typename": "CheckRun", "name": "CI", "conclusion": "SUCCESS"},
204+
{"__typename": "StatusContext", "context": "CI", "state": "FAILURE"},
205+
]
206+
)
207+
self.assertEqual(states["CI"], "failed")
208+
209+
value = entry(1)
210+
value.checks = states
211+
value.classify(CONFIG)
212+
213+
self.assertEqual(value.state, "blocked")
214+
self.assertIn("CI failed", value.reasons)
215+
216+
def test_same_name_check_run_and_pending_status_waits_required_check(self) -> None:
217+
states = check_states(
218+
[
219+
{"__typename": "CheckRun", "name": "CI", "conclusion": "SUCCESS"},
220+
{"__typename": "StatusContext", "context": "CI", "state": "PENDING"},
221+
]
222+
)
223+
self.assertEqual(states["CI"], "pending")
224+
225+
value = entry(1)
226+
value.checks = states
227+
value.classify(CONFIG)
228+
229+
self.assertEqual(value.state, "waiting")
230+
self.assertIn("CI is not complete", value.reasons)
231+
200232
def test_review_verdicts_are_classified_generically(self) -> None:
201233
value = entry(1)
202234
value.review_verdicts = (ReviewVerdict("Any bot", "blocked", ("one finding",)),)

0 commit comments

Comments
 (0)