← reports

#2048 · A pull request in a GitHub merge queue reads as ready to merge, and bb offers a merge action the queue already owns

Bug Priority: Medium Effort: n/a (not on project board) ui workspaces open on GitHub 2026-08-24 · base 494f66526

Verdict: REPRODUCED (live, against real GitHub merge-queue entries) · Root-cause confidence: high

1. TL;DR

When a GitHub repository uses a merge queue, a pull request that has been added to the queue is no longer something a human should merge by hand: the queue will merge it once its batch checks pass. bb reads pull-request state exclusively through gh pr view --json <fixed field list> on the host daemon, and that field list (indeed, the whole gh pr view --json surface) has no merge-queue field. The server then derives mergeability from mergeable/mergeStateStatus alone. A queued PR whose queue checks have passed comes back as mergeable: MERGEABLE, so bb labels it "Ready to merge" and renders the green Merge split button while GitHub itself shows an ochre Queued badge. A queued PR whose queue checks are still running comes back UNKNOWN/UNKNOWN; bb shows it as a plain green "Open · Checks passing" PR with no queue indicator at all (the CLI prints Merge: unknown). I reproduced both cases live on 2026-08-24 against the public repo ai-ecoverse/slicc, which had three PRs in its queue at the time, through bb's real daemon → server → web/CLI path on a dev instance.

2. Claims vs findings

Claim from the issueStatusEvidence
bb shows no sign that a PR has been queuedVerifiedLive: workspace on queued PR #2383 (queue position 1). Web shows "Open · Checks passing" (screenshot); CLI thread show prints Attention: none … Merge: unknown. No "queued" anywhere. GitHub shows "Queued" for the same PR (screenshot of #2385).
A queued PR that passed its pre-merge checks reports mergeable: MERGEABLE and bb classifies it mergeable / ready_to_merge and renders the green Merge split buttonVerifiedLive on #2385 (queue position 3, isInMergeQueue: true, gh: UNSTABLE/MERGEABLE). Server API returned mergeability.state: "mergeable", attention: "ready_to_merge"; banner rendered the "Merge" split button (screenshot). CLI: Attention: ready_to_merge … Merge: mergeable.
A queued PR whose queue checks are still running reports mergeStateStatus: UNKNOWN and lands in mergeability.state = "unknown"VerifiedLive on #2383 and #2380 (both AWAITING_CHECKS): gh → UNKNOWN/UNKNOWN; server → mergeability.state: "unknown", attention: "none" (API response).
…which renders as "Mergeability unknown" with an amber warning triangleRefuted (for web and mobile)The label exists in MERGEABILITY_DISPLAY.unknown on web and mobile, but neither surface renders it for this state: the metadata row only consults the mergeability display for conflicts/blocked (ThreadMetadataContent.tsx#L460-L470), and mobile's attention map likewise. What the user actually sees is a green "Open · Checks passing" row indistinguishable from a non-queued PR. The only surface that exposes "unknown" verbatim is the CLI (Merge: unknown). The underlying state claim is correct; the rendering claim is not.
gh pr view --json offers no merge-queue field (fixed list verbatim)Verifiedgh pr view 1 -R get-bb/bb --json isInMergeQueue → Unknown JSON field: "isInMergeQueue"; the printed field list on gh 2.85.0 matches the issue's list exactly (no isInMergeQueue/mergeQueueEntry).
isInMergeQueue and mergeQueueEntry exist only on the GraphQL PullRequest typeVerifiedGraphQL introspection of PullRequest lists isInMergeQueue, isMergeQueueEnabled, mergeQueue, mergeQueueEntry; MergeQueueEntry has position, state, enqueuedAt, estimatedTimeToMerge, solo, jump.
GitHub's MergeStateStatus enum has no queued memberVerifiedIntrospection: DIRTY, UNKNOWN, BLOCKED, BEHIND, UNSTABLE, HAS_HOOKS, CLEAN. (bb's schema additionally lists DRAFT, which is not in the live enum; harmless.)
Not draft-related; draft PRs are classified correctlyVerified (by code)assembleThreadPullRequestMergeability checks isDraft first; not exercised live.
Not fixable in a plugin; needs a HOST_DAEMON_PROTOCOL_VERSION bumpVerifiedThe data originates in the daemon's gh call and crosses the wire as the .strict() gitHostPullRequestSchema (thread.ts#L224-L240); any new field changes the workspace.pull_request RPC result. Current version: 164.
Version: bb 0.38.0 at 6da2a7a9Verified still present at basegit log 6da2a7a9..494f66526 on the relevant files shows only unrelated commits (#2169, #2252, #2140, #2057); origin/main has nothing beyond base for these paths. Not fixed.
Expected: queue position reported (e.g. #2241 carries position 4)Unverified as requirementProduct expectation, not a defect claim. The data is available from mergeQueueEntry.position (live: #2385 → position 3, estimatedTimeToMerge: 1919).

3. Environment

4. Minimal reproduction

Two layers: (A) a live end-to-end run through the real daemon/server/UI, which needs a repository with PRs currently in its merge queue; (B) a deterministic unit repro of the server assembler with the captured raw values, which needs nothing.

A. Live (daemon → server → web/CLI)

  1. Find queued PRs (read-only). ai-ecoverse/slicc had three at 17:13Z:
    $ gh api graphql -f query='{repository(owner:"ai-ecoverse",name:"slicc"){mergeQueue{entries(first:20){nodes{position state pullRequest{number mergeStateStatus mergeable isInMergeQueue}}}}}}'
    [{"position":1,"state":"AWAITING_CHECKS","pullRequest":{"number":2383,"mergeStateStatus":"UNKNOWN","mergeable":"UNKNOWN","isInMergeQueue":true}},
     {"position":2,"state":"AWAITING_CHECKS","pullRequest":{"number":2380,"mergeStateStatus":"UNKNOWN","mergeable":"UNKNOWN","isInMergeQueue":true}},
     {"position":3,"state":"AWAITING_CHECKS","pullRequest":{"number":2385,"mergeStateStatus":"UNSTABLE","mergeable":"MERGEABLE","isInMergeQueue":true}}]
    Script: poll-merge-queue.sh; full log: queue-poll.log.
  2. Run the exact read bb's daemon makes (field list from GH_PR_VIEW_JSON_FIELDS) — script save-gh-views.sh, raw output saved as gh-pr-view-2383.json, gh-pr-view-2385.json:
    $ gh pr view 2383 -R ai-ecoverse/slicc --json number,title,state,url,isDraft,baseRefName,headRefName,updatedAt,statusCheckRollup,reviewDecision,reviewRequests,mergeStateStatus,mergeable | jq -c '{number,state,mergeStateStatus,mergeable}'
    {"number":2383,"state":"OPEN","mergeStateStatus":"UNKNOWN","mergeable":"UNKNOWN"}
    $ gh pr view 2385 ... | jq -c '{number,state,mergeStateStatus,mergeable}'
    {"number":2385,"state":"OPEN","mergeStateStatus":"UNSTABLE","mergeable":"MERGEABLE"}
    $ gh api graphql -f query='{repository(owner:"ai-ecoverse",name:"slicc"){pullRequest(number:2385){isInMergeQueue mergeQueueEntry{position state enqueuedAt estimatedTimeToMerge}}}}'
    {"data":{"repository":{"pullRequest":{"isInMergeQueue":true,"mergeQueueEntry":{"position":3,"state":"AWAITING_CHECKS","enqueuedAt":"2026-08-24T17:12:42Z","estimatedTimeToMerge":1919}}}}}
  3. Feed those files through bb's own parser and assembler (assemble-live-gh-output.mjs, run with the worktree's tsx):
    PR #2383 state=OPEN gh(mergeStateStatus=UNKNOWN, mergeable=UNKNOWN)    -> mergeability=unknown   checks=passing attention=none           mergeButtonShown=false
    PR #2385 state=OPEN gh(mergeStateStatus=UNSTABLE, mergeable=MERGEABLE) -> mergeability=mergeable checks=passing attention=ready_to_merge mergeButtonShown=true
    Expected: both carry a queued state; neither is mergeable/ready_to_merge.
  4. Same thing through the running product. Start a dev instance, clone slicc under /tmp on a queued PR's head branch, register it as a project, spawn a thread on that path:
    $ git clone --filter=blob:none --no-checkout https://github.com/ai-ecoverse/slicc.git /tmp/bb2048-slicc
    $ git -C /tmp/bb2048-slicc fetch origin feat/ipk-global-bootstrap-hints:feat/ipk-global-bootstrap-hints && git -C /tmp/bb2048-slicc checkout feat/ipk-global-bootstrap-hints   # PR #2383's head
    $ git -C /tmp/bb2048-slicc branch --set-upstream-to=origin/feat/ipk-global-bootstrap-hints
    $ scripts/bb-dev-app current            # prints App/Server/Host daemon URLs
    $ curl -s -X POST $BB_SERVER_URL/api/v1/projects -H 'content-type: application/json' \
        -d '{"name":"slicc-qa","source":{"type":"local_path","path":"/tmp/bb2048-slicc","hostId":"host_bz7dbnad3p"}}'
    $ pnpm bb:dev thread spawn --project proj_aesm74wkuh --environment /tmp/bb2048-slicc --provider codex --permission-mode accept-edits --prompt "Reply only with ok." --json
    $ pnpm bb:dev thread show thr_gtfpf2sb8f
      Environment: Working locally (env_bfft5yfefw)
        Pull request: #2383 open - fix(shell): recommend global ipk add for CLI tool bootstraps
        Branch:       feat/ipk-global-bootstrap-hints -> main
        Attention:    none
        Checks:       passing (32 passed, 0 failed, 0 pending, 32 total)
        Review:       none (0 requested)
        Merge:        unknown                     <-- PR is at position 1 in the merge queue; nothing says so
    $ curl -s $BB_SERVER_URL/api/v1/environments/env_bfft5yfefw/pull-request | jq -c '{n:.pullRequest.number, mergeability:.pullRequest.mergeability, attention:.pullRequest.attention}'
    {"n":2383,"mergeability":{"state":"unknown","mergeStateStatus":"UNKNOWN","mergeable":"UNKNOWN"},"attention":"none"}
  5. Switch the same workspace to the head branch of the queued-but-MERGEABLE PR and read again (server PR cache TTL is 10 s):
    $ git -C /tmp/bb2048-slicc fetch origin bb/release-gate-timeout:bb/release-gate-timeout && git -C /tmp/bb2048-slicc checkout bb/release-gate-timeout   # PR #2385's head
    $ git -C /tmp/bb2048-slicc branch --set-upstream-to=origin/bb/release-gate-timeout
    $ curl -s $BB_SERVER_URL/api/v1/environments/env_bfft5yfefw/pull-request | jq -c '{n:.pullRequest.number, checks:.pullRequest.checks.state, mergeability:.pullRequest.mergeability, attention:.pullRequest.attention}'
    {"n":2385,"checks":"passing","mergeability":{"state":"mergeable","mergeStateStatus":"UNSTABLE","mergeable":"MERGEABLE"},"attention":"ready_to_merge"}
    $ pnpm bb:dev thread show thr_gtfpf2sb8f
        Pull request: #2385 open - ci: give release-gate a timeout that can clear a whole release
        Attention:    ready_to_merge
        Checks:       passing (36 passed, 0 failed, 0 pending, 36 total)
        Merge:        mergeable                   <-- PR is at position 3 in the merge queue
    Expected: a queued state with position 3 and no merge offer. Actual: "ready_to_merge"/"mergeable", and the web banner shows the Merge split button.
bb thread view with PR #2385 showing Ready to merge and a Merge split button
Step 5 in the web app: the prompt-context banner (bottom centre) shows PR #2385 with a green Merge ▾ split button, and the Info panel's Pull request row reads #2385 · Open · Ready to merge. #2385 is at position 3 in GitHub's merge queue at this moment.
Close-up of the banner with the Merge button
Close-up of the banner for the queued PR: the merge action is live (clicking it would run gh pr merge --merge on a PR the queue owns).
GitHub PR page for #2385 showing the ochre Queued badge
GitHub's own page for the same PR at the same time: ochre Queued badge where an ordinary open PR would show green "Open".
bb thread view with PR #2383 showing Open and Checks passing, no queue indicator
Step 4 in the web app (queue position 1, queue checks running): banner shows a plain green PR #2383 pill, Info panel shows #2383 · Open · Checks passing. There is no "Queued" and no "Mergeability unknown" either; it looks exactly like a non-queued open PR.

B. Deterministic unit repro (fails on base)

File: apps/server/src/services/environments/pull-request.merge-queue.repro.test.ts. Run from apps/server: pnpm exec vitest run src/services/environments/pull-request.merge-queue.repro.test.ts. All three assertions fail on 494f66526 (full output):

 ❯  @bb/server  src/services/environments/pull-request.merge-queue.repro.test.ts (3 tests | 3 failed) 4ms
     × queue entry AWAITING_CHECKS (gh: UNKNOWN/UNKNOWN) should not read as 'Mergeability unknown'
     × queue entry QUEUED (gh: UNSTABLE/MERGEABLE) must not be offered as mergeable
     × the raw contract carries no merge-queue information at all
AssertionError: expected 'unknown' not to be 'unknown'       // pull-request.merge-queue.repro.test.ts:57
AssertionError: expected 'mergeable' not to be 'mergeable'   // pull-request.merge-queue.repro.test.ts:75
AssertionError: expected false to be true                    // pull-request.merge-queue.repro.test.ts:85 (no key of GitHostPullRequest mentions the queue)
import type { GitHostPullRequest } from "@bb/domain";
import { describe, expect, it } from "vitest";
import { assembleThreadPullRequest } from "./pull-request.js";

function queuedPullRequest(
  overrides: Partial<GitHostPullRequest> = {},
): GitHostPullRequest {
  return {
    number: 2383,
    title: "fix(shell): recommend global ipk add for CLI tool bootstraps",
    state: "OPEN",
    url: "https://github.com/ai-ecoverse/slicc/pull/2383",
    isDraft: false,
    baseRefName: "main",
    headRefName: "feat/ipk-global-bootstrap-hints",
    updatedAt: "2026-08-24T16:21:46Z",
    // The PR's own checks have all completed; the merge-group checks run on
    // a temporary merge commit and never show up in the PR's rollup.
    checks: [
      {
        name: "ci",
        status: "completed",
        conclusion: "success",
        url: "https://github.com/ai-ecoverse/slicc/actions/runs/32750391366/job/97510979631",
        startedAt: "2026-08-24T16:38:55Z",
      },
    ],
    reviewDecision: null,
    reviewRequestCount: 0,
    mergeStateStatus: "UNKNOWN",
    mergeable: "UNKNOWN",
    ...overrides,
  };
}

describe("assembleThreadPullRequest with a merge-queued pull request (#2048)", () => {
  it("queue entry AWAITING_CHECKS (gh: UNKNOWN/UNKNOWN) should not read as 'Mergeability unknown'", () => {
    const assembled = assembleThreadPullRequest(queuedPullRequest());
    expect(assembled.mergeability.state).not.toBe("unknown");   // actual: "unknown"
    expect(assembled.attention).not.toBe("none");               // actual: "none"
  });

  it("queue entry QUEUED (gh: UNSTABLE/MERGEABLE) must not be offered as mergeable", () => {
    const assembled = assembleThreadPullRequest(
      queuedPullRequest({
        number: 2241,
        url: "https://github.com/ai-ecoverse/slicc/pull/2241",
        mergeStateStatus: "UNSTABLE",
        mergeable: "MERGEABLE",
      }),
    );
    expect(assembled.mergeability.state).not.toBe("mergeable");  // actual: "mergeable"
    expect(assembled.attention).not.toBe("ready_to_merge");      // actual: "ready_to_merge"
  });

  it("the raw contract carries no merge-queue information at all", () => {
    const keys = Object.keys(queuedPullRequest());
    expect(keys.some((key) => /queue/i.test(key))).toBe(true);  // actual: false
  });
});

Repro files: 2048/repro/ (scripts, raw gh captures, doobie screenshot scripts).

5. Root cause

The merge-queue state never enters the system. The host daemon's only PR read is one gh pr view --json call with a fixed field list (git-host.ts#L32-L46, invoked at #L643-L661):

const GH_PR_VIEW_JSON_FIELDS = [
  "number", "title", "state", "url", "isDraft", "baseRefName", "headRefName", "updatedAt",
  "statusCheckRollup", "reviewDecision", "reviewRequests", "mergeStateStatus", "mergeable",
].join(",");

gh pr view --json cannot be asked for isInMergeQueue or mergeQueueEntry at all (verified: Unknown JSON field: "isInMergeQueue" on gh 2.85.0; the issue shows the same on 2.97.0), and GitHub's MergeStateStatus enum has no queued member (verified by introspection), so no combination of the fields bb already fetches identifies a queued PR. The normalized wire contract gitHostPullRequestSchema is .strict() with no queue field, so the server cannot even be handed the information.

The server then classifies on incomplete data. assembleThreadPullRequestMergeability:

} else if (raw.mergeable === "MERGEABLE" || raw.mergeStateStatus === "CLEAN") {
  state = "mergeable";        // <- #2385 / #2241: queue entry, gh says UNSTABLE/MERGEABLE
} else {
  state = "unknown";          // <- #2383 / #2380 / #2239: queue entry, gh says UNKNOWN/UNKNOWN
}

and assemblePullRequestAttention promotes mergeable + checks.state === "passing" to ready_to_merge. A queued PR's own statusCheckRollup is its head-commit checks, which typically all passed (that is how it got into the queue); the merge-group checks run on a temporary merge commit and are not in the PR's rollup. So the "passing" precondition is naturally satisfied for queued PRs, which is why ready_to_merge is the common outcome rather than an edge case. (Why GitHub reports UNSTABLE alongside MERGEABLE for a queue entry is GitHub's business; bb cannot rely on it, and it is not a queued signal anyway — UNSTABLE also appears for ordinary PRs with a failing non-required check.)

Clients gate the merge action on exactly that state. Web banner ThreadPromptContextBanner.tsx#L1008-L1010 (pullRequest.state === "open" && pullRequest.mergeability.state === "mergeable" → PullRequestMergeSplitButton), mobile resolvePullRequestBannerAction, and the server-side guard for the merge route assertCanMergePullRequest all agree, so the merge action is not only displayed but would be executed (gh pr merge --merge) on a PR the queue owns. For the unknown branch, the web/mobile PR row falls through to the checks display ("Checks passing") because the mergeability display is consulted only for conflicts/blocked (ThreadMetadataContent.tsx#L460-L470), and the pill colours by PullRequestState only (PullRequestStatusPill.tsx#L7-L31), so a queued PR is visually identical to an ordinary open one.

Deeper issue. The product model (ThreadPullRequestMergeabilityState / ThreadPullRequestAttentionState) has no concept of "merge in progress by the host"; it only knows draft/open/merged/closed plus mergeability. Auto-merge (autoMergeRequest, which is a gh pr view --json field) has the same shape of problem: a PR with auto-merge enabled is also "owned" by GitHub and would likewise be offered for manual merge. A fix that adds only isInMergeQueue solves this issue but leaves that sibling case.

6. Proposed fix (first principles)

Confident about the cause. Keep the daemon returning raw host data and let the server own the policy, per AGENTS.md:

  1. Daemon (packages/host-workspace/src/git-host.ts): after the existing gh pr view read succeeds, make one additional read-only call gh api graphql -f query='query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){isInMergeQueue mergeQueueEntry{position state enqueuedAt estimatedTimeToMerge}}}}' (owner/name from headRepository/url of the first read, or add headRepositoryOwner,headRepository to the field list). Normalize into a new raw field mergeQueue: { position: number; state: "QUEUED"|"AWAITING_CHECKS"|"MERGEABLE"|"UNMERGEABLE"|"LOCKED"; enqueuedAt: string|null; estimatedTimeToMergeSeconds: number|null } | null on GitHostPullRequest. Treat a GraphQL failure as mergeQueue: null with the existing "unavailable" semantics only if the primary read also failed; do not let a missing queue read turn a found PR into "unavailable". Same 10 s timeout and env sanitization as the first call. Consider also surfacing autoMergeRequest (already in the gh pr view field list) as autoMerge: { method } | null.
  2. Contract: add the field to gitHostPullRequestSchema (required, nullable; no optional), and bump HOST_DAEMON_PROTOCOL_VERSION (164 → 165) in packages/host-daemon-contract/src/protocol.ts since the workspace.pull_request result shape changes.
  3. Server policy (apps/server/src/services/environments/pull-request.ts): in assembleThreadPullRequestMergeability, check raw.mergeQueue !== null right after the draft branch and return a new state "queued" (before conflicts/blocked, because a queued PR that the queue later rejects flips mergeQueueEntry to UNMERGEABLE/gets dequeued, and until then GitHub's page still says "Queued"). Add "queued" to threadPullRequestMergeabilityStateSchema and threadPullRequestAttentionStateSchema; in assemblePullRequestAttention return "queued" before ready_to_merge (after merged/closed, arguably before conflicts/checks_failed since the queue's verdict supersedes the PR-head checks). Carry mergeQueue: { position, state, estimatedTimeToMergeSeconds } on ThreadPullRequestMergeability so clients can say "Queued · #3". Make assertCanMergePullRequest in routes/environments.ts reject queued (it already rejects anything but mergeable, so this follows automatically once the state is not mergeable).
  4. Clients: add queued entries to MERGEABILITY_DISPLAY/ATTENTION_DISPLAY on web (apps/app/src/lib/pull-request-display.ts) and mobile (apps/mobile/src/data/environments/pull-request-display.ts) using the existing --attention ochre token; show "Queued · 3rd" (position) in the metadata row and banner attention label; the merge gate in the banner and resolvePullRequestBannerAction already key on mergeable, so the button disappears without further change. Keep refetching while queued (extend getEnvironmentPullRequestRefetchInterval to treat queued like pending). Update the CLI thread show output (it prints mergeability.state verbatim, so "queued" appears for free; add the position) and the SDK/CLI docs per AGENTS.md.
  5. Tests: the repro test above, turned positive; a git-host.test.ts case for the GraphQL normalizer including a failing GraphQL call; a workspace-read-cache-routes case that the merge route returns 409/400 for a queued PR.

What could go wrong: a second network call per poll doubles the gh cost of the PR probe (the server caches 10 s; the app polls every 30 s while active) and counts against the GraphQL rate limit — acceptable, but the call should be skipped when the first read says the PR is not OPEN. Repositories where merge queue is disabled return isInMergeQueue: false and mergeQueueEntry: null, so the default path is unchanged. Older daemons without the field will be rejected by the version bump and auto-update, which is the intended behaviour.

7. PR review

No open pull requests are linked to this issue; none found by search (gh pr list --search "merge queue" returns only unrelated PRs).

8. Related issues

9. Appendix

Verifying the gh surface and GitHub schema (read-only)

$ gh --version
gh version 2.85.0 (2026-01-14)
$ gh pr view 1 -R get-bb/bb --json isInMergeQueue
Unknown JSON field: "isInMergeQueue"
Available fields:
  additions assignees author autoMergeRequest baseRefName baseRefOid body changedFiles closed closedAt
  closingIssuesReferences comments commits createdAt deletions files fullDatabaseId headRefName headRefOid
  headRepository headRepositoryOwner id isCrossRepository isDraft labels latestReviews maintainerCanModify
  mergeCommit mergeStateStatus mergeable mergedAt mergedBy milestone number potentialMergeCommit projectCards
  projectItems reactionGroups reviewDecision reviewRequests reviews state statusCheckRollup title updatedAt url

$ gh api graphql -f query='{__type(name:"MergeStateStatus"){enumValues{name}}}'
DIRTY UNKNOWN BLOCKED BEHIND UNSTABLE HAS_HOOKS CLEAN

$ gh api graphql -f query='{__type(name:"PullRequest"){fields{name}}}' | tr ',' '\n' | grep -i queue
isInMergeQueue isMergeQueueEnabled mergeQueue mergeQueueEntry

$ gh api graphql -f query='{__type(name:"MergeQueueEntryState"){enumValues{name description}}}'
QUEUED "The entry is currently queued." / AWAITING_CHECKS "The entry is currently waiting for checks to pass." /
MERGEABLE "The entry is currently mergeable." / UNMERGEABLE / LOCKED

Public repositories probed for live queue entries

probe-public-merge-queues.sh → output. Of 20 repositories, only ai-ecoverse/slicc had entries at the time; github/docs, primer/react, rust-lang/cargo, pnpm/pnpm, tldraw/tldraw have queues enabled but were empty.

Full live captures

Commands run (abridged, in order)

git checkout 494f66526; pnpm install --frozen-lockfile --prefer-offline; pnpm exec turbo run build
gh issue view 2048 -R get-bb/bb --json ...                       # issue body; no comments
gh pr view 1 -R get-bb/bb --json isInMergeQueue                   # field-list check
gh api graphql ... __type(name:"MergeStateStatus"|"PullRequest"|"MergeQueueEntry"|"MergeQueueEntryState")
bash 2048/repro/probe-public-merge-queues.sh                      # find a live queue
bash 2048/repro/read-queued-prs.sh ai-ecoverse/slicc              # raw captures
bash 2048/repro/poll-merge-queue.sh ai-ecoverse/slicc 45 60 &     # background poll
git clone --filter=blob:none --no-checkout https://github.com/ai-ecoverse/slicc.git /tmp/bb2048-slicc
scripts/bb-dev-app current; curl -X POST .../api/v1/projects; pnpm bb:dev thread spawn ...; pnpm bb:dev thread show ...
cd apps/server && pnpm exec vitest run src/services/environments/pull-request.merge-queue.repro.test.ts   # 3 failed (expected)
bash 2048/repro/save-gh-views.sh <worktree> ai-ecoverse/slicc 2383 2380 2385                             # parser + assembler
doobie --headless < 2048/repro/shot-thread-{4,6,7,8}.js; doobie --headless < 2048/repro/shot-github.js   # screenshots
pnpm dev:stop; rm -rf ~/.bb-dev/<instance> /tmp/bb2048-slicc                                           # cleanup

Unused screenshots kept for completeness: first load (skeleton), 1400px load, 1800px load, pill hover, row hover, panel clip, #2383 banner clip.