From 7688cb2e0c36685a55d8b6ca7ea94b32ae36feb6 Mon Sep 17 00:00:00 2001 From: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:46:20 +0000 Subject: [PATCH 1/2] fix(#6201): detect merge-queue membership regardless of labels The merge-queue check in nextwork was gated on the ready-for-merge label in three places: maybe_check_merge_queue(), classify_pr(), and the post-classification reclassify loop in main(). PRs enqueued through other paths (e.g. manual approval of a requires-manual-review PR) were never checked and kept reporting stale statuses like needs_review_decision instead of the terminal waiting_merge_queue. Move the in_merge_queue check to the top of classify_pr() so it fires before any label-specific branch. Remove the ready-for-merge gate from maybe_check_merge_queue() so all PRs are checked. Narrow the reclassify loop to only re-run classification for PRs actually found in the merge queue (in_merge_queue=True) rather than gating on a label. Note: pre-commit could not run (sandbox network block); the post-script runs it authoritatively. Closes #6201 --- skills/nextwork/scripts/nextwork.py | 22 ++++++++------- skills/nextwork/scripts/nextwork_test.py | 35 +++++++++++++++++++----- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/skills/nextwork/scripts/nextwork.py b/skills/nextwork/scripts/nextwork.py index 6ddfdf50bf..9cf4375d5c 100644 --- a/skills/nextwork/scripts/nextwork.py +++ b/skills/nextwork/scripts/nextwork.py @@ -920,6 +920,14 @@ def classify_pr( eliminated=True, ) + # Merge-queue membership is authoritative regardless of labels. + if item.get("in_merge_queue"): + return Classification( + status="waiting_merge_queue", + reason="Already enqueued in the merge queue", + eliminated=True, + ) + inflight = classify_inflight_agent(comments, stale_hours, now) if inflight: return inflight @@ -980,12 +988,6 @@ def classify_pr( ) if "ready-for-merge" in labels: - if item.get("in_merge_queue"): - return Classification( - status="waiting_merge_queue", - reason="Already enqueued in the merge queue", - eliminated=True, - ) if checks_pending: return Classification( status="waiting_ci", @@ -1927,9 +1929,9 @@ def _resolve_linked(r=repo, n=number, fn=get_linked) -> list[int]: def maybe_check_merge_queue(items: list[dict[str, Any]], fetcher: MergeQueueChecker) -> None: - """Second pass: only hits the merge-queue API for PRs labeled ready-for-merge.""" + """Second pass: check merge-queue membership for all open PRs.""" for item in items: - if item["kind"] == "pull" and "ready-for-merge" in item.get("labels", []): + if item["kind"] == "pull": item["in_merge_queue"] = fetcher.is_in_merge_queue( item["repo"], item["number"], @@ -2552,9 +2554,9 @@ def main(argv: list[str] | None = None) -> None: quiet=args.quiet, ) maybe_check_merge_queue(items, fetcher) - # Merge-queue membership can change ready_to_merge -> waiting_merge_queue; reclassify. + # Merge-queue membership can change any status -> waiting_merge_queue; reclassify. for item in items: - if item["kind"] == "pull" and "ready-for-merge" in item.get("labels", []): + if item["kind"] == "pull" and item.get("in_merge_queue"): classification = classify_item( item, user, diff --git a/skills/nextwork/scripts/nextwork_test.py b/skills/nextwork/scripts/nextwork_test.py index f43913e2bd..368878f46c 100644 --- a/skills/nextwork/scripts/nextwork_test.py +++ b/skills/nextwork/scripts/nextwork_test.py @@ -1174,6 +1174,20 @@ def test_waiting_merge_queue(self): self.assertEqual(result.status, "waiting_merge_queue") self.assertTrue(result.eliminated) + def test_waiting_merge_queue_without_ready_for_merge_label(self): + """Merge-queue detection works regardless of which labels are present.""" + item = make_pr(labels=["requires-manual-review"], in_merge_queue=True) + result = classify_pr(item, "alice", 6, NOW) + self.assertEqual(result.status, "waiting_merge_queue") + self.assertTrue(result.eliminated) + + def test_waiting_merge_queue_no_labels(self): + """Merge-queue detection works even with no control labels at all.""" + item = make_pr(labels=[], in_merge_queue=True) + result = classify_pr(item, "alice", 6, NOW) + self.assertEqual(result.status, "waiting_merge_queue") + self.assertTrue(result.eliminated) + def test_waiting_fix_bot_author(self): item = make_pr( author="fullsend-ai-coder[bot]", @@ -2212,15 +2226,18 @@ def test_apply_with_confirmed_ok(self): class TestMaybeCheckMergeQueue(unittest.TestCase): - def test_sets_flag_only_for_ready_for_merge_prs(self): + def test_checks_all_prs_regardless_of_labels(self): class StubFetcher: + def __init__(self): + self.calls = [] + def is_in_merge_queue(self, repo, number, *, base_branch=None): - self.seen = (repo, number, base_branch) - return number == 2 + self.calls.append((repo, number, base_branch)) + return number == 1 stub = StubFetcher() items = [ - {"kind": "pull", "repo": "a/b", "number": 1, "labels": ["ready-for-review"]}, + {"kind": "pull", "repo": "a/b", "number": 1, "labels": ["requires-manual-review"]}, { "kind": "pull", "repo": "a/b", @@ -2231,10 +2248,14 @@ def is_in_merge_queue(self, repo, number, *, base_branch=None): {"kind": "issue", "repo": "a/b", "number": 3, "labels": ["ready-for-merge"]}, ] maybe_check_merge_queue(items, stub) - self.assertNotIn("in_merge_queue", items[0]) - self.assertTrue(items[1]["in_merge_queue"]) - self.assertEqual(stub.seen, ("a/b", 2, "release")) + self.assertTrue(items[0]["in_merge_queue"]) + self.assertFalse(items[1]["in_merge_queue"]) + # Issues are skipped. self.assertNotIn("in_merge_queue", items[2]) + # Both PRs checked (regardless of labels), issue skipped. + self.assertEqual(len(stub.calls), 2) + self.assertIn(("a/b", 1, None), stub.calls) + self.assertIn(("a/b", 2, "release"), stub.calls) class TestFetchErrors(unittest.TestCase): From 6e60f3b79e724c3a6428e4206155478803d1086a Mon Sep 17 00:00:00 2001 From: fullsend-fix <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Sun, 16 Aug 2026 07:57:56 +0000 Subject: [PATCH 2/2] fix(nextwork): update stale docs after merge-queue label gate removal - Update SKILL.md Limitations section to reflect that merge-queue membership is now checked for all open PRs (not just ready-for-merge) - Improve docstring precision in maybe_check_merge_queue() Addresses review feedback on #6203 --- skills/nextwork/SKILL.md | 6 ++---- skills/nextwork/scripts/nextwork.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/skills/nextwork/SKILL.md b/skills/nextwork/SKILL.md index 19f7b99e01..885f2f9b5d 100644 --- a/skills/nextwork/SKILL.md +++ b/skills/nextwork/SKILL.md @@ -228,10 +228,8 @@ like production dispatch: first whitespace token of the first comment line. itself is older than `--stale-hours` (default 6h); raw age still uses `--triage-stale-hours` (default 72h). - `waiting_ci` and `waiting_merge_queue` are not flipped by `--stale-hours`. -- Merge-queue membership is only checked for PRs labeled `ready-for-merge` - (to avoid an extra API call per PR); the check uses the PR's `baseRefName` - when available (not only the repo default branch). Other PRs never report - `in_merge_queue`. +- Merge-queue membership is checked for all open PRs; the check uses the + PR's `baseRefName` when available (not only the repo default branch). - Linked-PR detection scans open PRs only when an issue reaches that check (after blockers / assignment / sub-issues). The scan is capped at five GraphQL pages (~500 PRs) per repo; beyond that, some links may be missed. diff --git a/skills/nextwork/scripts/nextwork.py b/skills/nextwork/scripts/nextwork.py index 9cf4375d5c..7bc82d0958 100644 --- a/skills/nextwork/scripts/nextwork.py +++ b/skills/nextwork/scripts/nextwork.py @@ -1929,7 +1929,7 @@ def _resolve_linked(r=repo, n=number, fn=get_linked) -> list[int]: def maybe_check_merge_queue(items: list[dict[str, Any]], fetcher: MergeQueueChecker) -> None: - """Second pass: check merge-queue membership for all open PRs.""" + """Second pass: hits the merge-queue API for all open PRs.""" for item in items: if item["kind"] == "pull": item["in_merge_queue"] = fetcher.is_in_merge_queue(