From 167873b0e596c2845567b84b3a471e1931b50ebf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 18:13:34 +0900 Subject: [PATCH 1/5] fix(scheduler): recheck cancellation races --- scripts/ci/current_head_run_coalescer.py | 18 ++++++++++++++- tests/test_current_head_run_coalescer.py | 28 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/scripts/ci/current_head_run_coalescer.py b/scripts/ci/current_head_run_coalescer.py index ae40b85ac4..fd1553c016 100644 --- a/scripts/ci/current_head_run_coalescer.py +++ b/scripts/ci/current_head_run_coalescer.py @@ -373,7 +373,23 @@ def _fetch_run(repo: str, run_id: int) -> dict[str, Any]: def _cancel_run(repo: str, run_id: int) -> None: """Cancel one run and prove GitHub reached its terminal cancelled state.""" - _run_json(["gh", "api", "-X", "POST", f"repos/{repo}/actions/runs/{run_id}/cancel"]) + cancel_args = ["gh", "api", "-X", "POST", f"repos/{repo}/actions/runs/{run_id}/cancel"] + try: + _run_json(cancel_args) + except RuntimeError as exc: + # GitHub can race a queued run into startup between the candidate + # fetch and POST, returning HTTP 409 instead of accepting cancel. + # Re-read the authoritative run state before deciding whether a + # single retry is safe; never turn an unknown cancellation error into + # a successful result. + if "not been queued yet" not in str(exc): + raise + current = _fetch_run(repo, run_id) + if current.get("status") == "completed" and current.get("conclusion") == "cancelled": + return + if current.get("status") not in ACTIVE_STATUSES: + raise RuntimeError(f"workflow run {run_id} is no longer cancellable after HTTP 409") from exc + _run_json(cancel_args) for attempt in range(CANCELLATION_POLL_ATTEMPTS): run_data = _fetch_run(repo, run_id) if run_data.get("status") == "completed" and run_data.get("conclusion") == "cancelled": diff --git a/tests/test_current_head_run_coalescer.py b/tests/test_current_head_run_coalescer.py index 571368677f..e259a1fb54 100644 --- a/tests/test_current_head_run_coalescer.py +++ b/tests/test_current_head_run_coalescer.py @@ -473,6 +473,34 @@ def test_cancel_run_uses_explicit_transport_and_ordinary_endpoint(monkeypatch) - assert sleeps == [module.CANCELLATION_POLL_INTERVAL_SECONDS] +def test_cancel_run_rechecks_and_retries_when_queued_run_start_races(monkeypatch) -> None: + """A startup race gets one authoritative retry instead of failing the scheduler.""" + module = load_module() + cancel_calls = 0 + states = iter( + [ + {"status": "in_progress", "conclusion": None}, + {"status": "completed", "conclusion": "cancelled"}, + ] + ) + + def run_json(args): + nonlocal cancel_calls + if args[-1].endswith("/cancel"): + cancel_calls += 1 + if cancel_calls == 1: + raise RuntimeError("gh: Cannot cancel a workflow run that has not been queued yet. (HTTP409)") + return {} + raise AssertionError(args) + + monkeypatch.setattr(module, "_run_json", run_json) + monkeypatch.setattr(module, "_fetch_run", lambda _repo, _run_id: next(states)) + monkeypatch.setattr(module.time, "sleep", lambda _seconds: None) + + module._cancel_run("o/r", 123) + assert cancel_calls == 2 + + def test_cancel_run_fails_when_terminal_cancellation_is_unproven(monkeypatch) -> None: """An accepted cancellation is not reported complete while GitHub stays active.""" module = load_module() From 73579dd3bbccda67533a35151fd24e1ca7920fa3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 18:16:54 +0900 Subject: [PATCH 2/5] test(scheduler): bound cancellation race outcomes --- scripts/ci/current_head_run_coalescer.py | 3 +- tests/test_current_head_run_coalescer.py | 69 ++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/scripts/ci/current_head_run_coalescer.py b/scripts/ci/current_head_run_coalescer.py index fd1553c016..d8cc4a49e0 100644 --- a/scripts/ci/current_head_run_coalescer.py +++ b/scripts/ci/current_head_run_coalescer.py @@ -29,6 +29,7 @@ API_TIMEOUT_SECONDS = 30 CANCELLATION_POLL_ATTEMPTS = 6 CANCELLATION_POLL_INTERVAL_SECONDS = 1.0 +QUEUE_START_RACE_RE = re.compile(r"\bHTTP\s*409\b") class CoalescingRefused(RuntimeError): @@ -382,7 +383,7 @@ def _cancel_run(repo: str, run_id: int) -> None: # Re-read the authoritative run state before deciding whether a # single retry is safe; never turn an unknown cancellation error into # a successful result. - if "not been queued yet" not in str(exc): + if "not been queued yet" not in str(exc) or not QUEUE_START_RACE_RE.search(str(exc)): raise current = _fetch_run(repo, run_id) if current.get("status") == "completed" and current.get("conclusion") == "cancelled": diff --git a/tests/test_current_head_run_coalescer.py b/tests/test_current_head_run_coalescer.py index e259a1fb54..8729d988a3 100644 --- a/tests/test_current_head_run_coalescer.py +++ b/tests/test_current_head_run_coalescer.py @@ -501,6 +501,75 @@ def run_json(args): assert cancel_calls == 2 +@pytest.mark.parametrize( + ("state", "error", "expected_posts", "expected_gets"), + [ + ({"status": "completed", "conclusion": "cancelled"}, None, 1, 1), + ({"status": "completed", "conclusion": "success"}, "no longer cancellable", 1, 1), + ({"status": "mystery", "conclusion": None}, "no longer cancellable", 1, 1), + ], +) +def test_cancel_run_409_state_gate_never_overclaims( + monkeypatch, state, error, expected_posts, expected_gets +) -> None: + """Only cancelled terminal evidence suppresses the retry and success claim.""" + module = load_module() + calls = {"post": 0, "get": 0} + + def run_json(args): + if args[-1].endswith("/cancel"): + calls["post"] += 1 + raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") + raise AssertionError(args) + + def fetch_run(_repo, _run_id): + calls["get"] += 1 + return state + + monkeypatch.setattr(module, "_run_json", run_json) + monkeypatch.setattr(module, "_fetch_run", fetch_run) + if error: + with pytest.raises(RuntimeError, match=error): + module._cancel_run("o/r", 123) + else: + module._cancel_run("o/r", 123) + assert calls == {"post": expected_posts, "get": expected_gets} + + +def test_cancel_run_ignores_unrelated_error_without_recheck(monkeypatch) -> None: + """A non-409 cancellation error cannot trigger a compensating mutation.""" + module = load_module() + calls: list[str] = [] + + def run_json(args): + calls.append("post") + raise RuntimeError("HTTP500 upstream failure") + + monkeypatch.setattr(module, "_run_json", run_json) + monkeypatch.setattr(module, "_fetch_run", lambda *_args: calls.append("get")) + with pytest.raises(RuntimeError, match="HTTP500"): + module._cancel_run("o/r", 123) + assert calls == ["post"] + + +def test_cancel_run_fails_closed_when_retry_also_hits_queue_start_race(monkeypatch) -> None: + """A second startup race is never reported as a successful cancellation.""" + module = load_module() + calls = {"post": 0} + + def run_json(args): + if args[-1].endswith("/cancel"): + calls["post"] += 1 + raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") + raise AssertionError(args) + + monkeypatch.setattr(module, "_run_json", run_json) + monkeypatch.setattr(module, "_fetch_run", lambda *_args: {"status": "in_progress"}) + with pytest.raises(RuntimeError, match="HTTP409"): + module._cancel_run("o/r", 123) + assert calls == {"post": 2} + + def test_cancel_run_fails_when_terminal_cancellation_is_unproven(monkeypatch) -> None: """An accepted cancellation is not reported complete while GitHub stays active.""" module = load_module() From 2b5fc45dd23cead525a5330cd72361ea10f90f6d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 19:10:07 +0900 Subject: [PATCH 3/5] fix(scheduler): preserve runs started during cancellation --- scripts/ci/current_head_run_coalescer.py | 14 +++- tests/test_current_head_run_coalescer.py | 86 +++++++++++++++++++++--- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/scripts/ci/current_head_run_coalescer.py b/scripts/ci/current_head_run_coalescer.py index d8cc4a49e0..f9b8376d6a 100644 --- a/scripts/ci/current_head_run_coalescer.py +++ b/scripts/ci/current_head_run_coalescer.py @@ -388,9 +388,17 @@ def _cancel_run(repo: str, run_id: int) -> None: current = _fetch_run(repo, run_id) if current.get("status") == "completed" and current.get("conclusion") == "cancelled": return - if current.get("status") not in ACTIVE_STATUSES: - raise RuntimeError(f"workflow run {run_id} is no longer cancellable after HTTP 409") from exc - _run_json(cancel_args) + if current.get("status") != "queued": + raise CoalescingRefused(f"workflow run {run_id} is no longer queued after HTTP 409") from exc + try: + _run_json(cancel_args) + except RuntimeError as retry_exc: + if not ("not been queued yet" in str(retry_exc) and QUEUE_START_RACE_RE.search(str(retry_exc))): + raise + current = _fetch_run(repo, run_id) + if current.get("status") == "completed" and current.get("conclusion") == "cancelled": + return + raise CoalescingRefused(f"workflow run {run_id} remained uncancellable after HTTP 409") from retry_exc for attempt in range(CANCELLATION_POLL_ATTEMPTS): run_data = _fetch_run(repo, run_id) if run_data.get("status") == "completed" and run_data.get("conclusion") == "cancelled": diff --git a/tests/test_current_head_run_coalescer.py b/tests/test_current_head_run_coalescer.py index 8729d988a3..415f7bdc0d 100644 --- a/tests/test_current_head_run_coalescer.py +++ b/tests/test_current_head_run_coalescer.py @@ -473,13 +473,37 @@ def test_cancel_run_uses_explicit_transport_and_ordinary_endpoint(monkeypatch) - assert sleeps == [module.CANCELLATION_POLL_INTERVAL_SECONDS] -def test_cancel_run_rechecks_and_retries_when_queued_run_start_races(monkeypatch) -> None: - """A startup race gets one authoritative retry instead of failing the scheduler.""" +def test_cancel_run_preserves_started_run_after_cancel_409(monkeypatch) -> None: + """A run that started after the first POST is preserved without a second POST.""" module = load_module() cancel_calls = 0 states = iter( [ {"status": "in_progress", "conclusion": None}, + ] + ) + + def run_json(args): + nonlocal cancel_calls + if args[-1].endswith("/cancel"): + cancel_calls += 1 + raise RuntimeError("gh: Cannot cancel a workflow run that has not been queued yet. (HTTP409)") + raise AssertionError(args) + + monkeypatch.setattr(module, "_run_json", run_json) + monkeypatch.setattr(module, "_fetch_run", lambda _repo, _run_id: next(states)) + with pytest.raises(module.CoalescingRefused, match="no longer queued"): + module._cancel_run("o/r", 123) + assert cancel_calls == 1 + + +def test_cancel_run_retries_once_when_409_state_is_still_queued(monkeypatch) -> None: + """A queued run gets one compensating cancellation request after HTTP 409.""" + module = load_module() + cancel_calls = 0 + states = iter( + [ + {"status": "queued", "conclusion": None}, {"status": "completed", "conclusion": "cancelled"}, ] ) @@ -489,7 +513,7 @@ def run_json(args): if args[-1].endswith("/cancel"): cancel_calls += 1 if cancel_calls == 1: - raise RuntimeError("gh: Cannot cancel a workflow run that has not been queued yet. (HTTP409)") + raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") return {} raise AssertionError(args) @@ -501,12 +525,52 @@ def run_json(args): assert cancel_calls == 2 +def test_coalesce_preserves_started_candidate_after_cancel_409(monkeypatch, capsys) -> None: + """The production coalesce path preserves a candidate that starts at POST time.""" + module = load_module() + candidate = run_record(100, 10) + sibling = run_record(101, 10) + candidate_fetches = 0 + cancel_calls = 0 + + monkeypatch.setattr(module, "_fetch_pr", lambda *_args: live_pr()) + monkeypatch.setattr(module, "_active_runs", lambda *_args: [candidate, sibling]) + + def fetch_run(_repo, run_id): + nonlocal candidate_fetches + if run_id == 101: + return sibling + candidate_fetches += 1 + return candidate if candidate_fetches == 1 else run_record(100, 10, status="in_progress") + + def run_json(args): + nonlocal cancel_calls + if args[-1].endswith("/cancel"): + cancel_calls += 1 + raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") + raise AssertionError(args) + + monkeypatch.setattr(module, "_fetch_run", fetch_run) + monkeypatch.setattr(module, "_run_json", run_json) + + assert module.coalesce( + "ContextualWisdomLab/.github", + 1, + "ContextualWisdomLab/.github", + "feature/current", + "a" * 40, + ) == [] + assert cancel_calls == 1 + assert "Preserving run 100" in capsys.readouterr().out + + @pytest.mark.parametrize( ("state", "error", "expected_posts", "expected_gets"), [ ({"status": "completed", "conclusion": "cancelled"}, None, 1, 1), - ({"status": "completed", "conclusion": "success"}, "no longer cancellable", 1, 1), - ({"status": "mystery", "conclusion": None}, "no longer cancellable", 1, 1), + ({"status": "in_progress", "conclusion": None}, "no longer queued", 1, 1), + ({"status": "completed", "conclusion": "success"}, "no longer queued", 1, 1), + ({"status": "mystery", "conclusion": None}, "no longer queued", 1, 1), ], ) def test_cancel_run_409_state_gate_never_overclaims( @@ -529,7 +593,7 @@ def fetch_run(_repo, _run_id): monkeypatch.setattr(module, "_run_json", run_json) monkeypatch.setattr(module, "_fetch_run", fetch_run) if error: - with pytest.raises(RuntimeError, match=error): + with pytest.raises(module.CoalescingRefused, match=error): module._cancel_run("o/r", 123) else: module._cancel_run("o/r", 123) @@ -564,8 +628,14 @@ def run_json(args): raise AssertionError(args) monkeypatch.setattr(module, "_run_json", run_json) - monkeypatch.setattr(module, "_fetch_run", lambda *_args: {"status": "in_progress"}) - with pytest.raises(RuntimeError, match="HTTP409"): + states = iter( + [ + {"status": "queued", "conclusion": None}, + {"status": "in_progress", "conclusion": None}, + ] + ) + monkeypatch.setattr(module, "_fetch_run", lambda *_args: next(states)) + with pytest.raises(module.CoalescingRefused, match="remained uncancellable"): module._cancel_run("o/r", 123) assert calls == {"post": 2} From ce4036bc2619d4c3bb64bd4ed1ee41609cd60516 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 19:50:20 +0900 Subject: [PATCH 4/5] fix: preserve runs after cancellation race --- scripts/ci/current_head_run_coalescer.py | 19 +++------ tests/test_current_head_run_coalescer.py | 51 ++++++++++++++---------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/scripts/ci/current_head_run_coalescer.py b/scripts/ci/current_head_run_coalescer.py index f9b8376d6a..948c80cd01 100644 --- a/scripts/ci/current_head_run_coalescer.py +++ b/scripts/ci/current_head_run_coalescer.py @@ -380,25 +380,18 @@ def _cancel_run(repo: str, run_id: int) -> None: except RuntimeError as exc: # GitHub can race a queued run into startup between the candidate # fetch and POST, returning HTTP 409 instead of accepting cancel. - # Re-read the authoritative run state before deciding whether a - # single retry is safe; never turn an unknown cancellation error into - # a successful result. - if "not been queued yet" not in str(exc) or not QUEUE_START_RACE_RE.search(str(exc)): + # Re-read the authoritative run state; never turn an unknown + # cancellation error into a successful result or another mutation. + if not QUEUE_START_RACE_RE.search(str(exc)): raise current = _fetch_run(repo, run_id) if current.get("status") == "completed" and current.get("conclusion") == "cancelled": return if current.get("status") != "queued": raise CoalescingRefused(f"workflow run {run_id} is no longer queued after HTTP 409") from exc - try: - _run_json(cancel_args) - except RuntimeError as retry_exc: - if not ("not been queued yet" in str(retry_exc) and QUEUE_START_RACE_RE.search(str(retry_exc))): - raise - current = _fetch_run(repo, run_id) - if current.get("status") == "completed" and current.get("conclusion") == "cancelled": - return - raise CoalescingRefused(f"workflow run {run_id} remained uncancellable after HTTP 409") from retry_exc + raise CoalescingRefused( + f"workflow run {run_id} remained queued after HTTP 409; preserving it" + ) from exc for attempt in range(CANCELLATION_POLL_ATTEMPTS): run_data = _fetch_run(repo, run_id) if run_data.get("status") == "completed" and run_data.get("conclusion") == "cancelled": diff --git a/tests/test_current_head_run_coalescer.py b/tests/test_current_head_run_coalescer.py index 415f7bdc0d..9faff87e3d 100644 --- a/tests/test_current_head_run_coalescer.py +++ b/tests/test_current_head_run_coalescer.py @@ -497,8 +497,8 @@ def run_json(args): assert cancel_calls == 1 -def test_cancel_run_retries_once_when_409_state_is_still_queued(monkeypatch) -> None: - """A queued run gets one compensating cancellation request after HTTP 409.""" +def test_cancel_run_preserves_queued_run_after_cancel_409(monkeypatch) -> None: + """A queued run gets no compensating cancellation request after HTTP 409.""" module = load_module() cancel_calls = 0 states = iter( @@ -512,17 +512,14 @@ def run_json(args): nonlocal cancel_calls if args[-1].endswith("/cancel"): cancel_calls += 1 - if cancel_calls == 1: - raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") - return {} + raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") raise AssertionError(args) monkeypatch.setattr(module, "_run_json", run_json) monkeypatch.setattr(module, "_fetch_run", lambda _repo, _run_id: next(states)) - monkeypatch.setattr(module.time, "sleep", lambda _seconds: None) - - module._cancel_run("o/r", 123) - assert cancel_calls == 2 + with pytest.raises(module.CoalescingRefused, match="remained queued"): + module._cancel_run("o/r", 123) + assert cancel_calls == 1 def test_coalesce_preserves_started_candidate_after_cancel_409(monkeypatch, capsys) -> None: @@ -576,7 +573,7 @@ def run_json(args): def test_cancel_run_409_state_gate_never_overclaims( monkeypatch, state, error, expected_posts, expected_gets ) -> None: - """Only cancelled terminal evidence suppresses the retry and success claim.""" + """Only cancelled terminal evidence suppresses the preservation refusal.""" module = load_module() calls = {"post": 0, "get": 0} @@ -616,8 +613,8 @@ def run_json(args): assert calls == ["post"] -def test_cancel_run_fails_closed_when_retry_also_hits_queue_start_race(monkeypatch) -> None: - """A second startup race is never reported as a successful cancellation.""" +def test_cancel_run_fails_closed_when_queued_after_queue_start_race(monkeypatch) -> None: + """A queued run after a startup race is preserved without a second POST.""" module = load_module() calls = {"post": 0} @@ -628,16 +625,28 @@ def run_json(args): raise AssertionError(args) monkeypatch.setattr(module, "_run_json", run_json) - states = iter( - [ - {"status": "queued", "conclusion": None}, - {"status": "in_progress", "conclusion": None}, - ] - ) - monkeypatch.setattr(module, "_fetch_run", lambda *_args: next(states)) - with pytest.raises(module.CoalescingRefused, match="remained uncancellable"): + monkeypatch.setattr(module, "_fetch_run", lambda *_args: {"status": "queued", "conclusion": None}) + with pytest.raises(module.CoalescingRefused, match="remained queued"): + module._cancel_run("o/r", 123) + assert calls == {"post": 1} + + +def test_cancel_run_409_detection_does_not_depend_on_provider_english(monkeypatch) -> None: + """A bare HTTP 409 still preserves a queued run without a second POST.""" + module = load_module() + calls = {"post": 0} + + def run_json(args): + if args[-1].endswith("/cancel"): + calls["post"] += 1 + raise RuntimeError("HTTP 409 conflict") + raise AssertionError(args) + + monkeypatch.setattr(module, "_run_json", run_json) + monkeypatch.setattr(module, "_fetch_run", lambda *_args: {"status": "queued"}) + with pytest.raises(module.CoalescingRefused, match="remained queued"): module._cancel_run("o/r", 123) - assert calls == {"post": 2} + assert calls == {"post": 1} def test_cancel_run_fails_when_terminal_cancellation_is_unproven(monkeypatch) -> None: From 663a488897115d66faf64d720fadb754f7229601 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 20:53:00 +0900 Subject: [PATCH 5/5] test: document coalescer callbacks --- tests/test_current_head_run_coalescer.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_current_head_run_coalescer.py b/tests/test_current_head_run_coalescer.py index 9faff87e3d..136b373539 100644 --- a/tests/test_current_head_run_coalescer.py +++ b/tests/test_current_head_run_coalescer.py @@ -393,6 +393,7 @@ def test_run_json_uses_token_timeout_decodes_success_and_bounds_failure(monkeypa seen: dict[str, object] = {} def success(*args, **kwargs): + """Return bounded JSON while recording the subprocess timeout.""" seen.update(kwargs) return SimpleNamespace(returncode=0, stdout='{"ok":true}', stderr="") @@ -401,6 +402,7 @@ def success(*args, **kwargs): assert seen["timeout"] == module.API_TIMEOUT_SECONDS def timeout(*_args, **_kwargs): + """Raise the subprocess timeout sentinel for transport mapping.""" raise subprocess.TimeoutExpired(cmd="gh", timeout=30) monkeypatch.setattr(module.subprocess, "run", timeout) @@ -434,6 +436,7 @@ def test_fetch_helpers_fail_closed_and_paginate(monkeypatch) -> None: calls: list[list[str]] = [] def pages(args): + """Return two paginated workflow-run pages and then an empty page.""" calls.append(list(args)) status = next(item.split("=", 1)[1] for item in args if item.startswith("status=")) page = int(next(item.split("=", 1)[1] for item in args if item.startswith("page="))) @@ -484,6 +487,7 @@ def test_cancel_run_preserves_started_run_after_cancel_409(monkeypatch) -> None: ) def run_json(args): + """Raise the queued-start race from the cancellation POST.""" nonlocal cancel_calls if args[-1].endswith("/cancel"): cancel_calls += 1 @@ -509,6 +513,7 @@ def test_cancel_run_preserves_queued_run_after_cancel_409(monkeypatch) -> None: ) def run_json(args): + """Raise the queued-start race while preserving the queued state.""" nonlocal cancel_calls if args[-1].endswith("/cancel"): cancel_calls += 1 @@ -534,6 +539,7 @@ def test_coalesce_preserves_started_candidate_after_cancel_409(monkeypatch, caps monkeypatch.setattr(module, "_active_runs", lambda *_args: [candidate, sibling]) def fetch_run(_repo, run_id): + """Return the sibling or transition the candidate to in-progress.""" nonlocal candidate_fetches if run_id == 101: return sibling @@ -541,6 +547,7 @@ def fetch_run(_repo, run_id): return candidate if candidate_fetches == 1 else run_record(100, 10, status="in_progress") def run_json(args): + """Raise the queued-start race without permitting unrelated commands.""" nonlocal cancel_calls if args[-1].endswith("/cancel"): cancel_calls += 1 @@ -578,12 +585,14 @@ def test_cancel_run_409_state_gate_never_overclaims( calls = {"post": 0, "get": 0} def run_json(args): + """Raise the cancellation race for each parameterized state.""" if args[-1].endswith("/cancel"): calls["post"] += 1 raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") raise AssertionError(args) def fetch_run(_repo, _run_id): + """Return the parameterized authoritative post-409 state.""" calls["get"] += 1 return state @@ -603,6 +612,7 @@ def test_cancel_run_ignores_unrelated_error_without_recheck(monkeypatch) -> None calls: list[str] = [] def run_json(args): + """Raise the unrelated cancellation failure without a second request.""" calls.append("post") raise RuntimeError("HTTP500 upstream failure") @@ -619,6 +629,7 @@ def test_cancel_run_fails_closed_when_queued_after_queue_start_race(monkeypatch) calls = {"post": 0} def run_json(args): + """Raise the queue-start race while counting cancellation posts.""" if args[-1].endswith("/cancel"): calls["post"] += 1 raise RuntimeError("Cannot cancel a workflow run that has not been queued yet. (HTTP409)") @@ -637,6 +648,7 @@ def test_cancel_run_409_detection_does_not_depend_on_provider_english(monkeypatc calls = {"post": 0} def run_json(args): + """Raise a bare HTTP 409 to test language-independent detection.""" if args[-1].endswith("/cancel"): calls["post"] += 1 raise RuntimeError("HTTP 409 conflict") @@ -753,6 +765,7 @@ def test_coalesce_refetches_candidate_last_and_preserves_started_run(monkeypatch monkeypatch.setattr(module, "_active_runs", lambda *_args: [candidate, sibling]) def fetch_run(_repo: str, run_id: int): + """Return the sibling while showing the candidate started meanwhile.""" return sibling if run_id == 101 else run_record(100, 10, status="in_progress") monkeypatch.setattr(module, "_fetch_run", fetch_run) @@ -849,6 +862,7 @@ def test_main_treats_coalescing_refused_as_a_safe_no_op(monkeypatch, capsys) -> ] def refuse(*_args: object) -> list[int]: + """Raise the safe coalescing refusal handled by the CLI entrypoint.""" raise module.CoalescingRefused("pull request head moved before duplicate classification") monkeypatch.setattr(module, "coalesce", refuse)