Skip to content

fix(ci): stop the merge-queue dashboard forking into duplicate issues - #10684

Merged
davidfirst merged 3 commits into
masterfrom
worktree-merge-queue-dashboard-dedupe
Sep 2, 2026
Merged

fix(ci): stop the merge-queue dashboard forking into duplicate issues#10684
davidfirst merged 3 commits into
masterfrom
worktree-merge-queue-dashboard-dedupe

Conversation

@davidfirst

@davidfirst davidfirst commented Sep 2, 2026

Copy link
Copy Markdown
Member

The merge-queue bot forked its dashboard into a second issue on 2026-09-01: the same GET /issues?labels=merge-queue&state=open lookup returned the dashboard at 13:56 and omitted it at 14:00 (the issue was open, labeled, never renamed — the listing is index-backed and was momentarily stale). The script took its "no dashboard exists" branch and created a new one. Since the listing is newest-first and the lookup used .find(), every run since moved to the new issue and the original was orphaned with a frozen queue table.

  • The lookup now returns all matching dashboards, oldest first, and the oldest open one is canonical — its URL is the "Details" link on every gate status, so it must not hop to whatever was created last.
  • Two independent read paths (REST listing + GraphQL) are queried every run and their candidates unioned, so neither index can hide a dashboard from the other. A partial miss matters as much as an empty one: dropping the oldest while keeping a newer duplicate would crown the duplicate and hide the original from cleanup.
  • Every candidate is then re-read by number — a direct record lookup rather than an index query — and re-checked for state, title, label and not being a PR. That also catches staleness in the other direction: a duplicate an earlier run already closed would otherwise be closed and commented on again on every run.
  • Extra dashboards are closed automatically with a comment pointing at the canonical one, so a duplicate that still slips through heals on the next run instead of lingering.

Verified with MERGE_QUEUE_DRY_RUN=true against the live repo, and with a stubbed harness covering the partial-miss, fully-stale, closed-duplicate, PR-masquerading-as-dashboard and deleted-candidate cases. The issue orphaned by the original incident has since been closed by hand, so only one dashboard is open and this change is a no-op for it.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Prevent duplicate merge-queue dashboard issues

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Confirms empty REST dashboard searches through GraphQL before creating an issue.
• Keeps the oldest dashboard canonical, preserving stable gate-status details links.
• Comments on and closes extra dashboards without failing queue reconciliation.
Diagram

sequenceDiagram
  participant Bot as Merge Queue
  participant REST as REST Issues
  participant GQL as GraphQL Issues
  participant Gates as Gate Statuses
  participant Canon as Canonical Issue
  participant Dupes as Duplicate Issues
  Bot->>REST: Find open dashboards
  alt REST finds dashboards
    REST-->>Bot: Return all matches
  else REST misses
    Bot->>GQL: Confirm absence
    GQL-->>Bot: Return matching issues
  end
  Bot->>Bot: Select oldest issue
  Bot->>Gates: Link canonical dashboard
  Bot->>Canon: Update queue table
  opt Extra dashboards exist
    Bot->>Dupes: Comment and close
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use GraphQL exclusively
  • ➕ Removes duplicate REST and GraphQL lookup implementations.
  • ➕ Avoids the REST endpoint returning pull requests alongside issues.
  • ➖ A single read path cannot independently confirm a stale miss.
  • ➖ Reduces resilience if the GraphQL connection is temporarily inconsistent or unavailable.
2. Persist the canonical issue number
  • ➕ Provides deterministic constant-time lookup.
  • ➕ Eliminates title-and-label discovery during normal reconciliation.
  • ➖ Introduces state and recovery procedures when the issue is closed or repository settings change.
  • ➖ Conflicts with the script's stateless, self-healing design.

Recommendation: Keep the PR's REST-first, GraphQL-confirmed approach. It preserves stateless reconciliation, maintains a stable oldest-dashboard URL, and automatically repairs duplicates without making cosmetic cleanup a queue failure.

Files changed (1) +71 / -4

Bug fix (1) +71 / -4
merge-queue.jsDeduplicate merge-queue dashboard issues +71/-4

Deduplicate merge-queue dashboard issues

• Returns every matching dashboard, confirms REST misses through GraphQL, and consistently selects the oldest issue as canonical. Newer duplicates are commented on and closed with best-effort failure handling and dry-run support.

.github/scripts/merge-queue.js

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Partial results replace canonical ✓ Resolved 🐞 Bug ≡ Correctness
Description
findDashboardIssues() accepts any non-empty REST result without GraphQL confirmation, so if REST
omits the oldest dashboard but returns a newer duplicate, the newer issue becomes canonical. This
changes every gate's Details URL, updates the wrong dashboard, and leaves the omitted original open
instead of healing the fork.
Code

.github/scripts/merge-queue.js[R538-540]

+  const dashboards = await findDashboardIssuesViaRest();
+  if (dashboards.length) return dashboards.sort((a, b) => a.number - b.number);
+  const confirmation = await findDashboardIssuesViaGraphql();
Evidence
The code documents that the oldest open dashboard must remain canonical, but GraphQL is only called
after REST returns zero matches. The first REST result is selected at lines 629-631, passed as the
Details URL at lines 703-713, updated at lines 608-614, and only dashboards present in that same
partial result can be closed at lines 739-741.

.github/scripts/merge-queue.js[528-548]
.github/scripts/merge-queue.js[629-631]
.github/scripts/merge-queue.js[703-713]
.github/scripts/merge-queue.js[608-614]
.github/scripts/merge-queue.js[738-741]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The REST lookup is trusted whenever it returns at least one dashboard. A partially stale response can omit the oldest dashboard while retaining a newer duplicate, causing the newer issue to become canonical despite the oldest-first invariant.
## Issue Context
Query both independent paths and reconcile their results by issue number before sorting oldest-first. Ensure a partial result from either path cannot replace the established canonical dashboard or hide a duplicate from cleanup.
## Fix Focus Areas
- .github/scripts/merge-queue.js[537-548]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread .github/scripts/merge-queue.js Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 2207332

@davidfirst
davidfirst enabled auto-merge (squash) September 2, 2026 19:29
@davidfirst
davidfirst merged commit 098c3c3 into master Sep 2, 2026
14 checks passed
@davidfirst
davidfirst deleted the worktree-merge-queue-dashboard-dedupe branch September 2, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants