---
title: PR Merge Queue
---

# PR Merge Queue

> **Scope:** this page explains the queue's interface and merge-session behavior. Immutable
> revisions, local landing, leased publication, GitHub settlement, author conversations, rollout,
> and recovery are owned by the
> [PR lifecycle front door](../../.claude/memory/front-doors/cat-pr-merge-queue.md).

## What it is

Omniscio's **PR Merge Queue** is an in-app view that surfaces every open GitHub pull request across the repos you've configured, classifies each one into a **lane** that reflects how safe / risky / batchable it is, and lets you spawn an Omniscio Claude CLI session to **prepare the merge** on a single click. The session runs in a fresh, isolated worktree on the PR's branch with a composed merge prompt (bring the PR in → resolve conflicts intelligently → run tests), then hands the prepared result to the operator lifecycle. The session never pushes a remote base branch. Local landing, leased relay publication, and independent GitHub `MERGED` settlement happen under separate authority after preparation.

The view is **manual by default** — Omniscio does the triage and the orchestration locally (so you see _why_ a PR is in each lane), and you click Merge on each card.

**The queue keeps itself fresh in the background.** Once the feature is enabled and you've configured at least one repo, a background poll re-fetches every repo's open PRs on a fixed interval (default 10 minutes) and saves the result. Opening the view is therefore **instant** — it shows the last saved data with an "Updated _Nm_ ago" freshness label and live-updates while you watch as new background refreshes land. You never have to click Refresh to get current data, though the manual Refresh button is still there for an on-demand pull. See **Background auto-refresh** below.

**The Inbox tells you when a PR needs you.** When a repo has a PR that needs a human decision, or a merge that got stuck, the unified Inbox shows **one self-clearing row per PR** — an amber "PR #_N_ needs review" or a red "PR #_N_ merge stuck". **Dismissing a row archives just that PR**; clicking it renders that PR's repo queue right in the Inbox detail pane — you stay in the Inbox; the row disappears on its own once the next background refresh finds it no longer pending. See **Inbox nudges** below.

**You can dismiss PRs you don't care about.** Each PR card has an **Archive** action — and each Inbox PR row can be dismissed directly (middle-click on desktop / the dismiss action on mobile) — that hides the PR from the list like archiving an email, without closing the PR on GitHub. An archived PR drops out of the Inbox **everywhere** — both the "needs review" count and the "merge stuck" signal — so dismissing one quiets it completely, not just in the open view. A transient **Show archived** toggle brings them back (dimmed, below the active pills) so you can **Unarchive** any you change your mind about. See **Archiving (dismissing) a PR** below.

**You can attach your own merge guidance.** Two levels: per-repo **standing instructions** that ride along with every merge for that repo, and an opt-in per-merge (or per-batch) box for guidance specific to one merge. Both are advisory — they never override the hard merge-safety rules. See **Custom merge instructions** on the [second page](pr-merge-queue-part-2.md).

**Related PRs can be prepared as a batch in one session.** When triage groups several non-overlapping PRs into the **Batch** lane, a single **Merge batch together** button spawns ONE session that applies all of them in dependency order onto a local branch and runs the test suite once at the end — leaving that branch ready for you to merge. (It never touches your base branch or pushes.) See **Batch merge** below.

**Opt-in: Auto-Merge Daemon.** On top of the always-on background refresh, a separately flag-gated daemon can auto-spawn merge sessions for fast-lane PRs on repos with `trustLevel: 'autonomous'`. Other lanes (standard, batch, conflict-likely, risky, hold, escalate, reject) and lower trust levels (`pause-and-ask`, `fast-lane-only`) still wait for an explicit click. A dry-run mode lets you preview decisions without spending; a per-repo daily budget cap (default $5 USD) skips spawning once today's cost (UTC day) hits the cap. Two further opt-ins reshape what a tick spawns: a **multi-machine split** (sharding) so several installs partition the same repo's PRs without ever double-merging one, and **auto-batch**, which groups several non-overlapping fast PRs into ONE session instead of one per PR — both default OFF, see **Settings shape**. The master flag defaults OFF and switching it ON requires an explicit confirmation modal. (Auto-refresh and auto-merge are independent: the background poll runs whenever the feature is on, regardless of whether auto-merge is enabled.)

## Where to find it

PR Merge Queue is switched on with one settings flag, and once it is on it appears in two places — a toolbar icon and a sidebar row — with a third, narrower mount inside the Inbox detail pane.

### Master flag

PR Merge Queue is a registry-driven Omniscio integration gated on **`AppSettings.prMergeQueueEnabled`** (default `false`). Toggle it at Settings → Features → "PR Merge Queue" (search-discoverable via the search index id `pr-merge-queue-enabled`).

**What the flag actually gates** — four surfaces, each with its own check:

| Surface                             | Gate                                                                                 |
| ----------------------------------- | ------------------------------------------------------------------------------------ |
| Toolbar `<GitPullRequest>` icon     | `visibleWhen: (s) => s.prMergeQueueEnabled === true` (`toolbar-catalog.ts`)          |
| Sidebar virtual project row         | hidden when the flag is off (`project-visibility.ts`)                                |
| CLI control-server routes           | `getSettings().prMergeQueueEnabled === true` (`cli-server-pr-merge-queue-routes.ts`) |
| Background poll / auto-merge daemon | the tick returns early when the flag is off (`auto-merge-daemon.ts`)                 |

With the flag ON: both UI surfaces appear, and the view renders an empty-state CTA when `prMergeQueueRepos[]` is empty so users always have a path forward.

**What turning it off does.** The toggle controls where the PR Merge Queue appears and whether it runs in the background. With it off, both UI surfaces are hidden and the background poll and auto-merge daemon return early without fetching or spawning anything — so nothing merges on its own. Starting a merge is desktop-only either way; see **Starting a merge is desktop-only** above.

### Two surfaces

PR Merge Queue is a registry-driven `virtual project` — it appears in two places once the master flag is ON:

- **Toolbar icon** — `<GitPullRequest>` button (pinned toolbar item id `'pr-merge-queue'`). Click it to open the view as an overlay. The bundle lazy-loads on first open.
- **Sidebar virtual project** — appears as a sidebar row alongside MemPalace, Bookmarks, etc. Clicking it activates the virtual project so the view occupies the main pane (no SessionsSidebar fallback — `panelOwnsLayout: true` in `INTEGRATION_UI_REGISTRY`).

Both surfaces mount the same `PrMergeQueueView` component — it accepts `LayoutSlotProps` (`width`, `borderSide`, `isMobile`) so the sidebar mount lays out edge-to-edge while the toolbar overlay keeps its compact framing.

A **third** mount renders `PrMergeQueueView` inside the **Inbox detail pane** — when a `pr-merge-queue` inbox row is selected it shows in-pane (not as a bounce-out to the virtual project). That mount passes `lockedProjectId` to pin the view to the one repo and hide the repo dropdown + close-X. See **Inbox nudges** below.

**Mobile layout.** The view renders one vertical list of PR pills on both desktop and mobile (full-width pills on a phone). There is no lane switcher — the single render path serves both form factors.

**Starting a merge is desktop-only (security).** On a phone you can browse the queue, refresh it, archive and unarchive PRs, and work the inbox rows exactly as on desktop — but the two buttons that SPAWN a merge session, **Merge** and **Merge batch together**, only work in the desktop app. Tapping them on a paired phone returns "Merging a pull request is only available in the desktop app."

Why: those two actions start a _billed_ AI session whose job is to land code, and they pass your free-text **Add instructions** straight into that agent's prompt. Web Access authorises a _device_ once, at pairing — it has no per-action confirmation — so a phone that was paired (or a leaked session on one) could otherwise start paid, code-landing agents with instructions of its own choosing. Everything read-only stays available on mobile; only the spend-and-land actions moved behind the desktop. Kick off the merge from the desktop app, then watch and steer the resulting session from your phone as usual.

## How it behaves

The view is manual by default: Omniscio does the triage and the orchestration locally, and you click Merge on each card. This section walks the day-to-day path — opening the view, reading the lanes, starting a merge — and everything that happens around it.

### How to use it

1. **Open the view.** Either click the toolbar `<GitPullRequest>` icon, or click the **PR Merge Queue** row in the sidebar (both surfaces require the master flag ON in Settings → Features). The view lazy-loads its bundle on first open.
2. **Pick a repo.** A dropdown at the top of the view lists every repo you've configured. The first time you open the view there will be none — you'll see an empty state telling you to add one in Settings → PR Merge Queue. Click **Add a repo**, pick an Omniscio project from the dropdown, and Omniscio will inspect the project folder's local git config (`git remote get-url origin` + `git symbolic-ref refs/remotes/origin/HEAD`) to pre-fill the GitHub slug, base branch, and display name. If the project isn't a GitHub remote (private GitLab, no remote set, etc.) the form stays blank and you can fill it in manually. The detection is local-git-only — no `gh` auth required at this stage (the `gh` requirement only kicks in for the actual queue refresh). The repo settings form also has a **Standing merge instructions** textarea — see **Custom merge instructions** on the [second page](pr-merge-queue-part-2.md).
3. **The lanes are already populated.** Because the background poll keeps a saved snapshot per repo, switching to a repo shows its last fetched queue immediately, with an **"Updated _Nm_ ago"** label next to the repo selector. You only click **Refresh** when you want an on-demand pull right now — it shells out to `gh pr list --json ...` and then `gh pr view <n>` per open PR, six at a time (progress streams as "Refreshing… (_enriched/total_)"; a 50-PR repo is usually a few seconds), re-runs triage + orchestration, and replaces the snapshot. If the most recent background refresh of a repo failed, the freshness label flags it so you know you're looking at last-good data.
4. **Read the lanes.** Each PR card shows author, title, size, CI status, mergeability, and the reasons it was placed in its lane. In the redesigned view the eight engine lanes are shown as **one vertical list of PR pills**, each tagged with one of three plain statuses — **Ready** (fast / standard / batch), **Needs your call** (risky / conflict-likely / escalate / hold), or **Blocked** (reject). The detailed lane still drives triage internally; the status is the user-facing label. The lanes are:
   - **fast** — small, low-risk, CI green: easy auto-merge candidate
   - **standard** — typical PR: needs human review then merge
   - **batch** — multiple small PRs that can be merged together
   - **conflict-likely** — overlaps another open PR in the same files
   - **risky** — touches paths matching your `riskyPaths` globs (DB migrations, auth code, etc.)
   - **hold** — depends on another PR that isn't merged yet
   - **escalate** — needs a more senior reviewer (size, surface area, controversy signals)
   - **reject** — failing CI, draft, or otherwise not mergeable
5. **Click the Merge button on any card.** A Blocked PR (draft, failing CI, or not mergeable) shows a quick **confirm** first ("Merge a blocked PR?"), then proceeds — the merge session still only prepares the worktree and never pushes or touches your base branch. Omniscio:
   - Creates the merge session WITHOUT building a worktree itself (`createSessionWithPrompt({ isolationOverride: false, source: 'pr-merge-queue' })`). Omniscio only _names_ the worktree path + records it; the **spawned agent builds it** (the old in-process `git worktree add` was a ~17k-file checkout that timed out under load and failed merges to even start — 2026-06-02, contract `the-agent-builds-its-own-worktree`).
   - Sends the composed merge prompt — its RULE 1 tells the agent to create its own worktree at that path, set up node_modules, then bring the PR in per the rule carried in the prompt itself. Omniscio runs no git for the merge.
   - **Spawns it in the background — it does NOT take over your current view.** A success toast confirms the spawn and carries an **Open** action to jump to the merge session if you want; otherwise you stay exactly where you were. (Auto-focusing the freshly-spawned session was the 2026-06-01 "PR Merge Queue took over my whole session" bug — see contract `a-spawned-merge-never-steals-the-active-session`).
   - **Optional per-merge guidance.** Each card has an **Add instructions** link (a `<MessageSquarePlus>` toggle) that reveals a small textarea ("Optional: extra guidance for this merge"). Leave it collapsed and Merge stays one-click; type something and it rides along as advisory context in the merge prompt. See **Custom merge instructions**.
6. **Prepare a whole batch at once (Batch lane).** When triage puts **2 or more** PRs in the **Batch** lane, a **Merge batch together (_N_)** bar appears at the top of the PR list. Click it and Omniscio spawns ONE session that applies all the grouped PRs in dependency order onto a local branch in a single worktree and runs the tests once at the end — then stops with that branch ready for you to merge, instead of one session per PR. The same **Add instructions** toggle lets you attach per-batch guidance. See **Batch merge**.
7. **Watch the session prepare the merge.** The prompt tells Claude to read the diff, bring the PR into the worktree (merging the PR's head when it is landing all of it, cherry-picking the author's commits when it is leaving anything out — never rebasing, which would rewrite the author's commits and cost them credit), **build that merge with the bounded construction rather than a bare `git merge --no-ff`** (`npm run pr:merge-bounded`, which runs the real merge and then puts every path outside the PR's own delta back to exactly what your base branch has, so a base that has moved on since the PR forked cannot drop or revert your newer work), **run `npm run audit:pr-merge-scope` as a MANDATORY check after every merge it performs** (a refusal is reported to you as a blocked PR and never as a silent drop), resolve conflicts by understanding intent (never blind ours/theirs), run tests if a command is supplied, and then **stop** — leaving the worktree ready for you to merge. It explicitly does **not** touch the base branch (`master`), does **not** push anything, and does **not** perform the merge: you do that final step yourself, with the conflict/test work already done. If Claude can't get the worktree ready safely, it writes a one-line reason to `./AMC_MQ_GIVE_UP.txt` in the worktree and exits. When it stops, its **final message leads with a plain-language, bulleted "What this PR does" summary** (plus a short "anything to watch out for" line) written for a non-technical reader, above the technical details — even on a give-up (it explains what it was trying to do and what blocked it). The default prompts bake this in; see **Editing the merge prompt** and contract `the-default-prompts-demand-a-plain-language-report`.

> **The merge procedure lives in the prompt itself.** Earlier builds told every merge session to
> assemble the PR with a `/merge-remote-prs` skill. That skill was **retired on 2026-09-06**: it
> assembled by cherry-picking or squashing, which never publishes the pull request's own head
> commit, so GitHub could never mark the PR merged and its author never got credit. The shipped
> prompt (`pr-merge-queue-prompt-templates.ts`)
> now carries the rule inline, so nothing has to be installed for a session to prepare a PR
> correctly. The one doctrine is
> `pr-merge-contract.md`.

### Background auto-refresh

The queue refreshes itself in the background so you don't have to click Refresh. A periodic **queue-poll** task re-fetches every configured repo's open PRs on a fixed interval and **persists each repo's result as a snapshot**. The view loads that saved snapshot the instant you open it (no GitHub round-trip on open), shows an **"Updated _Nm_ ago"** freshness label next to the repo selector, and live-updates while open as later background polls land.

Behavior in prose:

- **When it runs.** Whenever the feature is enabled (`prMergeQueueEnabled`) **and** at least one repo is configured. This is **independent of auto-merge** — turning auto-merge off does not stop the background refresh; the poll still keeps your snapshots fresh and your Inbox accurate.
- **How often.** The interval is the `prMergeQueueAutoMergePollMinutes` setting (default **10 minutes**, clamped 1–120). The same setting governs both the refresh cadence and the auto-merge daemon's tick — they share one timer. Changing it takes effect on the next Omniscio start (the running task keeps its boot-time interval).
- **What each tick does, per repo.** Fetches the snapshot via the same `refreshQueue` path the manual Refresh uses, recomputes the two attention counts, writes the snapshot row, and — only when the attention summary actually changed vs. the last row — pushes an update to any open view / the Inbox. An idle repo whose counts didn't move doesn't wake the UI. If a repo's fetch fails, the prior snapshot is kept (so the view still shows last-good data) and the freshness label flags the failure.
- **A tick only re-reads the PRs that actually changed.** Reading a PR's file list and CI status costs one GitHub call _per PR_, so a tick used to cost one call for the listing plus one for every open PR — measured at 182 calls on a repo with 181 open PRs, every 10 minutes. Each saved PR now records the commit it was built from, and a tick re-reads a PR only when that commit **or** its merge state has moved since last time. Those are the only two things that can make the saved detail wrong, and the cheap listing call already reports both, so everything else is reused as-is. A steady tick costs a handful of calls instead of hundreds — and because the snapshot is saved to disk, this holds **across an Omniscio restart** too. The result is unchanged: a PR whose commit moved, or whose checks finished, is always re-read.
- **Manual Refresh still works.** The Refresh button does an immediate on-demand pull and replaces the snapshot; it does not disable or reset the background timer.
- **Kill switch.** Power users can set the env var `AMC_DISABLE_PR_QUEUE_POLL=1` to make the tick a no-op (no polling at all), reverting to the old behavior where only the auto-merge daemon fetched (and only when auto-merge was on). The view then shows whatever was last saved and you refresh manually.

### Inbox nudges

When a repo needs your attention, the unified Inbox shows **one row per PR that needs you** — so you notice each one on the dashboard (and on mobile) without opening the queue, and can deal with them individually. Each row is one of two kinds:

- **A PR that needs a human decision** — a PR triaged into the review-needed lanes (**Escalate, Risky, Conflict-Likely, Hold**) shows an amber **"PR #_N_ needs review"** row. (A PR you've **archived**, or one that already has a **running merge session**, is excluded — see **Archiving (dismissing) a PR** and **Starting a merge clears the nudge** below.)
- **A merge that got stuck** — a merge session that gave up (wrote its give-up file), or an auto-merge run that failed, shows a red **"PR #_N_ merge stuck"** row.

A PR that is _both_ stuck and review-needed collapses to a **single** row (the red stuck row wins). Stuck rows sort above review rows within the same repo.

Key behaviors:

- **One row per PR**, with a stable id of `pr-merge-queue-<projectId>-<prNumber>`. (This replaced the old one-row-per-repo summary on 2026-06-02 so each PR can be dismissed on its own.)
- **Also shows in the repo's project view (not Inbox-only).** Because the rows are tied to the repo's real Omniscio project, the same nudge surfaces in that project's **"Needs You"** sidebar section — on desktop and mobile — and bumps the project's amber attention badge, so you see it whether you're in the Inbox or sitting in the project. It behaves like the session rows beside it: **Tab / J / K** (and mobile tap) reach it in render order, and clicking or keyboard-activating it opens the repo's queue **in place** in the project's main panel (the same surface the Inbox rows open) — you stay in the project, and closing it returns you there rather than the global Inbox; it self-clears together with the Inbox rows. This is the general "project-scoped inbox attention also appears in its project" rule — see `project-needs-you-attention-contract.md`.
- **Dismiss a row to archive that PR.** Dismissing a PR row (middle-click on desktop / the dismiss action on mobile) **archives that one PR** — the same per-repo Archive the queue view's card button performs, so it drops out of the lanes and quiets everywhere. There's no undo toast (matching Omniscio's other archive-style dismissals); recover it via the queue view's **Show archived → Unarchive**. See **Archiving (dismissing) a PR**.
- **Click to view in-pane.** Clicking the row renders that PR's repo merge queue in the Inbox detail pane and **keeps you in the Inbox** — it does NOT bounce out to the full-screen PR Merge Queue view. (All of a repo's PR rows open the same repo-locked pane — the click focuses the repo, the dismiss targets the one PR.) The pane reuses `PrMergeQueueView` locked to the one repo (`lockedProjectId`), so the repo dropdown + close-X are hidden; the full view stays reachable from the toolbar icon / sidebar row. Selection lives in the `activeInboxPrMergeQueueProjectId` store slice (mutex-paired with the other inbox-detail slices, like drip / scheduled-message). See `pr-merge-queue-contract.md` (rules `one-inbox-row-per-attention-needing-pr` / `a-row-opens-in-the-inbox-detail-pane`).
- **Snooze is still per-repo.** Snoozing any of a repo's PR rows routes through the universal inbox snooze gate and hides **all** of that repo's PR rows until the snooze expires.
- **Self-clears.** The rows are **derived purely from the saved snapshot** (enriched on read into the actual review-needed / stuck PR numbers), not from an ephemeral "should fire" flag. The instant the next background refresh finds a PR no longer review-needed or stuck, its row disappears on its own. Removing a repo from settings drops all its rows immediately. Because the rows and the view read the **same** snapshot, what you see in the Inbox always matches the queue.
- **Starting a merge clears the nudge.** The moment you click **Merge** (or **Merge batch together**) and a merge session is running for a PR, that PR is treated as _handled_ and its **"needs review"** row drops out **immediately** — without waiting for the next refresh. It does **not** wait for the PR to actually merge on GitHub: the merge session only _prepares_ the merge, so the PR still appears in the full queue view until it's merged/closed. If that merge session ends without finishing (you close it, it errors, or it gives up), the PR's row returns on the next background refresh — and a give-up shows up as the red **"merge stuck"** row in the meantime. (The exclusion uses the same session-aware "is a merge live for this PR?" check that stops a second merge being spawned for it — see **A PR being handled is never re-spawned**.)

### Archiving (dismissing) a PR

Sometimes a PR is open but you don't want it cluttering the queue — a long-lived draft you're tracking elsewhere, someone else's WIP, a PR you've decided to leave for later. **Archive** dismisses it from the queue the way archiving an email clears it from your inbox, **without closing the PR on GitHub**. It's a personal, per-repo preference — purely local to Omniscio.

- **Archive a PR.** Every active PR card has an **Archive** button — and every Inbox PR row can be dismissed (middle-click on desktop / the dismiss action on mobile), which archives that same PR. Either way the card disappears from the list immediately and your dismissal is saved per-repo in settings (`AppSettings.prMergeQueueArchivedPrs`), so the PR stays hidden across refreshes, repo switches, and app restarts — until you unarchive it.
- **Archive from the keyboard.** You don't have to reach for the mouse. With the queue focused, the **arrow keys** move a highlight between PR cards (it lands on the first card automatically when the queue opens) and the **archive key** — the same shortcut that archives elsewhere in Omniscio, **E** or **Ctrl+W** by default, or whatever you've rebound it to — archives the highlighted card and jumps to the next one, so you can clear several PRs in a row without the mouse. Pressing it on an archived card (with **Show archived** on) unarchives it instead. The key is scoped to the queue while it's focused, so it never archives one of your sessions by mistake, and typing into a card's **Add instructions** box is left alone. Locked by the rule **`keyboard-archive-stays-in-the-panel`** in `pr-merge-queue-contract.md`.
- **It quiets the Inbox too — review _and_ stuck.** An archived PR is dropped from the Inbox the instant you archive it — both its "needs review" row **and** its "merge stuck" row — recomputed from the saved snapshot with **no GitHub re-fetch**, so dismissing a PR stops it nagging you everywhere, not just in the open view. (Before 2026-06-02 a stuck merge was deliberately left showing even after archiving; the **`archive-clears-both-inbox-counts` reversal** changed that so archiving means "I've handled this" and clears the PR completely — dismissing a stuck PR's row is exactly how you make that failure signal go away.)
- **Show archived / Unarchive.** When a repo has any archived PRs, a **Show archived (_N_)** toggle appears in the header. Flip it on and the archived PRs reappear **dimmed, appended below every active pill** (not slotted back into position — the view is one flat list, so archived cards always render last), each with an **Unarchive** button that restores it to the queue (and brings it back into the review count). The toggle is a transient view preference — it resets to off when you close the view, like an email client's Archive folder, and is never persisted.
- **Everything else counts only active PRs.** The **Show archived (_N_)** count, the Inbox attention counts, and the **Merge batch together** button all operate on the **active** (non-archived) cards only — archiving a batchable PR shrinks the set that button will merge. Archiving is **orthogonal to triage**: a hide-and-recount filter layered on top of the list, never a lane of its own, so a PR keeps whatever lane triage gave it.
- **If you archive every PR in a repo**, the list is replaced by a one-line "All _N_ PRs in this repo are archived" hint with a **Show archived** link — so you're never left staring at a blank queue.
- **Works on mobile.** In the vertical-pill mobile view, the same Archive / Unarchive actions and the Show-archived toggle apply.

Stored as `AppSettings.prMergeQueueArchivedPrs` (a `projectId → PR-number[]` map). The per-repo count exclusions (both review and stuck) and the instant cache recompute live in `src/main/services/pr-merge-queue/attention-summary.ts`; both the queue view's Archive button and the Inbox row's dismiss write the whole next map through the same shared `setPrArchived` helper and the normal settings-save path, which fires the recompute. Because the list keys on PR number, a number that later closes is simply never matched again (harmless), and the per-repo 500-PR cap bounds the list. Locked by the rules **`archived-prs-are-a-filter-not-a-lane`**, **`archive-clears-both-inbox-counts`**, **`archiving-recomputes-from-cache-only`**, **`the-archive-write-sends-the-whole-map`**, **`the-inbox-lists-are-enriched-on-read`** and **`dismissing-a-row-archives-the-pr`** in `pr-merge-queue-contract.md`.

### Batch merge

When triage groups several **related, non-overlapping** PRs into the **Batch** lane, you can merge the whole set with one click instead of spawning a session per PR.

- **Where.** A **Merge batch together (_N_)** bar appears at the **top of the PR list** (the view is one flat list, not lane columns) — shown **only when 2 or more** PRs are batchable (a one-PR "batch" is just a normal merge).
- **What it does.** Clicking it spawns **ONE** session in **ONE** worktree that applies all the grouped PRs **in dependency order** (cherry-picking each, verifying each landed cleanly with no dropped commits and no leftover conflict markers), runs the **test suite once** after the entire set lands, and lands the result on a clearly-named local branch in the worktree — then **stops, ready for you to merge**. It never touches your base branch and never pushes anything. The PRs were grouped precisely because their file sets don't overlap, which is what makes "test once" safe.
- **Why it's safer than N sessions.** Independent diffs can't silently break each other, so a single green run after the whole set lands verifies all of them — and there's one integrate/test cost instead of _N_.
- **Per-batch guidance.** The same **Add instructions** opt-in box lets you add guidance for the whole batch (combined with the repo's standing instructions, same as the single-PR path).
- **Stop conditions.** On any unsafe state — an unexpected conflict, a failed verification check, two PRs introducing the same DB migration version, or a test failure it can't fix in 1–2 attempts — the session **stops and writes a give-up reason** rather than leaving a partial or unsafe set on the ready branch.

### Limitations (current)

- **N+1 calls on Refresh.** Each PR triggers a separate `gh pr view`. Repos with many open PRs are slow; progress is streamed via `PR_MERGE_QUEUE_REFRESH_PROGRESS`. (The background poll absorbs this cost off the critical path, so an open view rarely waits on it.)
- **Poll interval change requires restart.** Changing `prMergeQueueAutoMergePollMinutes` (which governs both the background refresh cadence and the auto-merge tick) takes effect on the next Omniscio start — the running task keeps its boot-time interval.
- **Batch grouping is triage-driven, not hand-picked.** The **Merge batch together** button merges exactly the PRs triage placed in the Batch lane; you can't currently hand-select an arbitrary subset to batch.

## For agents

The rest of the PR Merge Queue — what it persists, the in-flight guard that stops a PR being merged twice, custom and per-repo merge instructions, editing the merge prompt, the triage and orchestration modules, the lane decision tree, the IPC channels, the auto-merge daemon and AI summaries — is in [PR Merge Queue (part 2)](pr-merge-queue-part-2.md).

## Related

- [silent-recipe-sessions.md](silent-recipe-sessions.md) — concept of source-tagged sessions; PR Merge Queue sessions tag `source: 'pr-merge-queue'` (manual), `'pr-merge-queue-batch'` (batch), or `'pr-merge-queue-auto'` (daemon).
- `integration-contracts.md` — the virtual-project / sidebar-row / toolbar-icon registry contract this feature plugs into.
- Postmortem: `pr-merge-queue-postmortem.md` — build history, design decisions, DO NOT RETRY rules.
