From 70999cf0b6b1ae3ff3e73c2b538f858728ccde4f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 15:59:22 +0900 Subject: [PATCH 1/2] fix(scheduler): refresh stale branches before re-review --- scripts/ci/pr_review_merge_scheduler.py | 12 ++++++++++++ tests/test_pr_review_merge_scheduler.py | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index 75e18c860..f06bebd75 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -2388,6 +2388,18 @@ def request_branch_update(freshness_reason: str, *, suffix: str = "") -> Decisio return decide("block", f"{unresolved} unresolved review thread(s)") if has_current_head_changes_requested(pr): + behind_by = branch_outdated_by_base(pr, merge_state) + if ( + behind_by + and not pr.get("autoMergeRequest") + and update_branches + and trigger_reviews + and review_dispatch_allowed + and can_update_pr_head(repo, pr) + ): + return request_branch_update( + "current-head OpenCode review requested changes; branch is outdated before re-review" + ) if pr.get("autoMergeRequest"): return finish( disable_auto_merge_decision( diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 3e421e903..920d057ed 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -2997,6 +2997,27 @@ def test_inspect_pr_blocks_and_waits_for_policy_states(monkeypatch): assert inspect(make_pr(reviews={"nodes": [opencode_review("CHANGES_REQUESTED", "head")]})).reason == ( "current-head OpenCode review requested changes" ) + stale_change_request = inspect( + make_pr( + mergeStateStatus="BEHIND", + restMergeableState="BEHIND", + compareBehindBy=2, + reviews={"nodes": [opencode_review("CHANGES_REQUESTED", "head")]}, + ) + ) + assert stale_change_request.action == "update_branch" + assert stale_change_request.reason.startswith( + "current-head OpenCode review requested changes; branch is outdated before re-review" + ) + assert inspect( + make_pr( + mergeStateStatus="BEHIND", + restMergeableState="BEHIND", + compareBehindBy=2, + reviews={"nodes": [opencode_review("CHANGES_REQUESTED", "head")]}, + ), + trigger_reviews=False, + ).action == "block" action_required_pr = make_pr( statusCheckRollup={ "contexts": { From 907040d95e3b84cdc30e95a0976b4d25b1a67217 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 16:21:41 +0900 Subject: [PATCH 2/2] fix(scheduler): avoid update-branch on merge conflicts --- scripts/ci/pr_review_merge_scheduler.py | 3 +- tests/test_pr_review_merge_scheduler.py | 42 ++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index f06bebd75..118d0d903 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -2390,7 +2390,8 @@ def request_branch_update(freshness_reason: str, *, suffix: str = "") -> Decisio if has_current_head_changes_requested(pr): behind_by = branch_outdated_by_base(pr, merge_state) if ( - behind_by + merge_state not in {"DIRTY", "CONFLICTING"} + and behind_by and not pr.get("autoMergeRequest") and update_branches and trigger_reviews diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 920d057ed..f2dd25813 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -3006,10 +3006,11 @@ def test_inspect_pr_blocks_and_waits_for_policy_states(monkeypatch): ) ) assert stale_change_request.action == "update_branch" - assert stale_change_request.reason.startswith( - "current-head OpenCode review requested changes; branch is outdated before re-review" + assert stale_change_request.reason == ( + "current-head OpenCode review requested changes; branch is outdated before re-review; " + "branch update requested with workflow GITHUB_TOKEN inside GitHub Actions as github-actions[bot]" ) - assert inspect( + stale_change_request_without_review_dispatch = inspect( make_pr( mergeStateStatus="BEHIND", restMergeableState="BEHIND", @@ -3017,7 +3018,40 @@ def test_inspect_pr_blocks_and_waits_for_policy_states(monkeypatch): reviews={"nodes": [opencode_review("CHANGES_REQUESTED", "head")]}, ), trigger_reviews=False, - ).action == "block" + ) + assert stale_change_request_without_review_dispatch.action == "block" + assert stale_change_request_without_review_dispatch.reason == ( + "current-head OpenCode review requested changes" + ) + stale_change_request_without_dispatch_permission = inspect( + make_pr( + mergeStateStatus="BEHIND", + restMergeableState="BEHIND", + compareBehindBy=2, + reviews={"nodes": [opencode_review("CHANGES_REQUESTED", "head")]}, + ), + review_dispatch_allowed=False, + ) + assert stale_change_request_without_dispatch_permission.action == "block" + assert stale_change_request_without_dispatch_permission.reason == ( + "current-head OpenCode review requested changes" + ) + update_calls = [] + monkeypatch.setattr(sched, "update_branch", lambda *args, **kwargs: update_calls.append((args, kwargs))) + for merge_state in ("DIRTY", "CONFLICTING"): + conflict_with_stale_review = inspect( + make_pr( + mergeStateStatus=merge_state, + restMergeableState=merge_state, + compareBehindBy=2, + reviews={"nodes": [opencode_review("CHANGES_REQUESTED", "head")]}, + ) + ) + assert conflict_with_stale_review.action == "block" + assert conflict_with_stale_review.reason == ( + "current-head OpenCode review requested changes" + ) + assert update_calls == [] action_required_pr = make_pr( statusCheckRollup={ "contexts": {