← back to Cli Printing Press
chore(ci): enable parallel queue checks in Mergify (#1668)
019dcf3b98e3e4ee12611b7898e298787ff5ca93 · 2026-05-19 12:05:20 -0700 · Trevin Chow
* chore(ci): enable parallel queue checks in Mergify
Raise `merge_queue.max_parallel_checks` from 1 to 3 and split
`queue_conditions` / `merge_conditions` so Mergify can speculate on
multiple queued PRs in parallel via `mergify/merge-queue/*` draft PRs.
The prior config pinned both `batch_size` and `max_parallel_checks` to
1 under the belief that both were paywalled features on the OSS plan.
Mergify support clarified (2026-05-19) that only `batch_size > 1`
triggers the paid "Merge Queue Batch" feature; `max_parallel_checks`
is a separate, free knob. `batch_size` stays at 1.
Greptile compatibility, which was the secondary reason for the old
pinning, is handled by the new split:
- `queue_conditions` (gates entry on the source PR) keeps the Greptile
+ `#review-threads-unresolved = 0` requirements.
- `merge_conditions` (gates the merge after the speculative draft PR's
CI passes) requires only the CI checks that actually run on bot-owned
branches. Greptile silent-skips bot branches by design, so requiring
it here would block every queued PR.
- `merge_protections` continues to re-enforce Greptile + review-threads
on the source PR before the GitHub merge, as belt-and-suspenders.
* docs(ci): capture mergify parallel-checks learning under docs/solutions/
Add a knowledge-track learning doc that explains the corrected mental
model for Mergify on the OSS plan: `batch_size > 1` is paywalled (the
"Merge Queue Batch" feature), `max_parallel_checks > 1` is not, and
the three-block (queue_conditions / merge_conditions / merge_protections)
pattern is what makes draft_pr mode safe alongside Greptile, which
silent-skips bot branches by design.
Cross-references the wrong-turn history (#1306 raise, #1321 revert,
#1336 single-block narrowing) and the present fix (#1668) so the doc
is useful both as forward guidance and as context for why the prior
config was conservative.
* fix(ci): drop pr-title from mergify merge_conditions
Greptile flagged this as a P1 on PR #1668: `.github/workflows/pr-title.yml`
skips semantic validation only for titles starting with `merge queue:`
(the batch-mode prefix from #1306). In `draft_pr` mode with `batch_size: 1`,
Mergify's draft PR title format is not guaranteed to match that prefix,
so requiring `check-success = pr-title` on the draft could stall every
queued PR.
The source PR's title is already validated at `queue_conditions` entry
and cannot change while queued, so re-checking it on the speculative
draft is redundant. Drop it from `merge_conditions` and document the
trap in the comment block so the next person editing this config sees
why the omission is intentional.
Also update the learning doc with this as a third non-obvious failure
mode under "Why This Matters".
Files touched
M .mergify.ymlA docs/solutions/tooling-decisions/mergify-max-parallel-checks-vs-batch-size-2026-05-19.md
Diff
commit 019dcf3b98e3e4ee12611b7898e298787ff5ca93
Author: Trevin Chow <trevin@trevinchow.com>
Date: Tue May 19 12:05:20 2026 -0700
chore(ci): enable parallel queue checks in Mergify (#1668)
* chore(ci): enable parallel queue checks in Mergify
Raise `merge_queue.max_parallel_checks` from 1 to 3 and split
`queue_conditions` / `merge_conditions` so Mergify can speculate on
multiple queued PRs in parallel via `mergify/merge-queue/*` draft PRs.
The prior config pinned both `batch_size` and `max_parallel_checks` to
1 under the belief that both were paywalled features on the OSS plan.
Mergify support clarified (2026-05-19) that only `batch_size > 1`
triggers the paid "Merge Queue Batch" feature; `max_parallel_checks`
is a separate, free knob. `batch_size` stays at 1.
Greptile compatibility, which was the secondary reason for the old
pinning, is handled by the new split:
- `queue_conditions` (gates entry on the source PR) keeps the Greptile
+ `#review-threads-unresolved = 0` requirements.
- `merge_conditions` (gates the merge after the speculative draft PR's
CI passes) requires only the CI checks that actually run on bot-owned
branches. Greptile silent-skips bot branches by design, so requiring
it here would block every queued PR.
- `merge_protections` continues to re-enforce Greptile + review-threads
on the source PR before the GitHub merge, as belt-and-suspenders.
* docs(ci): capture mergify parallel-checks learning under docs/solutions/
Add a knowledge-track learning doc that explains the corrected mental
model for Mergify on the OSS plan: `batch_size > 1` is paywalled (the
"Merge Queue Batch" feature), `max_parallel_checks > 1` is not, and
the three-block (queue_conditions / merge_conditions / merge_protections)
pattern is what makes draft_pr mode safe alongside Greptile, which
silent-skips bot branches by design.
Cross-references the wrong-turn history (#1306 raise, #1321 revert,
#1336 single-block narrowing) and the present fix (#1668) so the doc
is useful both as forward guidance and as context for why the prior
config was conservative.
* fix(ci): drop pr-title from mergify merge_conditions
Greptile flagged this as a P1 on PR #1668: `.github/workflows/pr-title.yml`
skips semantic validation only for titles starting with `merge queue:`
(the batch-mode prefix from #1306). In `draft_pr` mode with `batch_size: 1`,
Mergify's draft PR title format is not guaranteed to match that prefix,
so requiring `check-success = pr-title` on the draft could stall every
queued PR.
The source PR's title is already validated at `queue_conditions` entry
and cannot change while queued, so re-checking it on the speculative
draft is redundant. Drop it from `merge_conditions` and document the
trap in the comment block so the next person editing this config sees
why the omission is intentional.
Also update the learning doc with this as a third non-obvious failure
mode under "Why This Matters".
---
.mergify.yml | 50 +++++--
...max-parallel-checks-vs-batch-size-2026-05-19.md | 164 +++++++++++++++++++++
2 files changed, 205 insertions(+), 9 deletions(-)
diff --git a/.mergify.yml b/.mergify.yml
index cd7bd3f8..3f827704 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -1,12 +1,22 @@
queue_rules:
- name: default
- # Single condition block (no separate merge_conditions) so Mergify uses
- # in-place mode instead of draft_pr mode. In-place mode rebases the
- # source PR's branch directly; CI re-runs on the source PR where
- # Greptile (and any other PR-only review apps) can actually post.
- # The draft `mergify/merge-queue/*` mechanism creates bot-owned branches
- # that Greptile skips, which was the root cause of every queued PR
- # timing out waiting for Greptile.
+ # Two-block setup (queue_conditions + merge_conditions) intentionally
+ # flips Mergify into draft_pr mode so it can speculate on multiple
+ # PRs in parallel (see merge_queue.max_parallel_checks below).
+ #
+ # The split is what makes draft_pr mode safe for our setup:
+ # - queue_conditions gate entry on the SOURCE PR -> Greptile and
+ # review-thread resolution are enforced here, against the human-
+ # reviewable PR where Greptile actually posts checks.
+ # - merge_conditions gate the actual merge after the speculative
+ # draft PR's CI passes -> only the CI checks that run on the
+ # bot-owned `mergify/merge-queue/*` branch are required. Greptile
+ # silent-skips bot branches by design (Mergify confirmed this is
+ # expected), so requiring it here would block every queued PR.
+ #
+ # merge_protections (further down) re-enforces Greptile + review-
+ # threads against the source PR before the GitHub merge actually
+ # happens, so the human-review gate isn't bypassed by speculation.
queue_conditions:
- base = main
# All Greptile/human inline review threads must be resolved before
@@ -30,6 +40,22 @@ queue_rules:
- and:
- head = release-please--branches--main
- "title ~= ^chore\\(main\\): release"
+ # Evaluated against the speculative draft PR. Intentionally omitted:
+ # - Greptile / review-threads — Greptile silent-skips bot branches,
+ # and draft PRs have no review threads; both are re-enforced on
+ # the source PR via queue_conditions + merge_protections.
+ # - pr-title — `.github/workflows/pr-title.yml` skips only titles
+ # starting with `merge queue:` (batch-mode prefix). In draft_pr
+ # mode the draft title format is not guaranteed to match, which
+ # would hang the check. Source PR title is already validated at
+ # queue_conditions entry and cannot change while queued, so the
+ # re-check on the draft is redundant.
+ merge_conditions:
+ - check-success = build-and-test
+ - check-success = generated-test
+ - check-success = go-lint
+ - check-success = golden
+ - check-success = test
merge_method: squash
# `update_method: merge` instead of `rebase` so the queue can update
# fork PRs without `update_bot_account`. Mergify deprecated the
@@ -39,12 +65,18 @@ queue_rules:
# in the queue, but `merge_method: squash` collapses it on the way
# to main, so the on-main history is unchanged.
update_method: merge
- # OSS plan: batch_size and max_parallel_checks must both be 1.
+ # OSS plan: batch_size must stay at 1 (raising it triggers the paid
+ # "Merge Queue Batch" feature and dequeues PRs with a billing block).
+ # max_parallel_checks below is a separate knob and is NOT paywalled.
batch_size: 1
checks_timeout: 60m
merge_queue:
- max_parallel_checks: 1
+ # Parallel speculation across queued PRs. Mergify creates up to N
+ # `mergify/merge-queue/*` draft PRs concurrently and runs CI on each.
+ # Free on the OSS plan; the paid feature is `batch_size > 1`, which
+ # groups multiple PRs into one queue position.
+ max_parallel_checks: 3
queued_label: queued
dequeued_label: dequeued
status_comments: outcomes
diff --git a/docs/solutions/tooling-decisions/mergify-max-parallel-checks-vs-batch-size-2026-05-19.md b/docs/solutions/tooling-decisions/mergify-max-parallel-checks-vs-batch-size-2026-05-19.md
new file mode 100644
index 00000000..2a8c7887
--- /dev/null
+++ b/docs/solutions/tooling-decisions/mergify-max-parallel-checks-vs-batch-size-2026-05-19.md
@@ -0,0 +1,164 @@
+---
+title: "Mergify max_parallel_checks is free; only batch_size is paywalled"
+date: 2026-05-19
+category: tooling-decisions
+module: ci/mergify
+problem_type: tooling_decision
+component: tooling
+severity: medium
+applies_when:
+ - "Tuning Mergify merge queue throughput on the OSS / free plan"
+ - "Greptile or other human-review tools silent-skip mergify/merge-queue/* bot branches and gate the queue"
+ - "Deciding whether to raise batch_size or max_parallel_checks to speed up a slow queue"
+related_components:
+ - development_workflow
+tags:
+ - mergify
+ - merge-queue
+ - max-parallel-checks
+ - batch-size
+ - greptile
+ - oss-plan
+ - queue-conditions
+ - merge-conditions
+---
+
+# Mergify max_parallel_checks is free; only batch_size is paywalled
+
+## Context
+
+`mvanhorn/cli-printing-press` runs on Mergify's free OSS plan. The merge queue had been pinned to the most conservative possible config — `batch_size: 1`, `max_parallel_checks: 1`, a single `queue_conditions` block — under the belief that both throughput knobs were paywalled features. They aren't. Only `batch_size > 1` is paid. `max_parallel_checks > 1` is free, and not using it leaves a real speedup on the floor.
+
+The wrong-turn history:
+
+- **PR #1306** raised `batch_size: 1 → 3` plus `speculative_checks: 2` (the older Mergify name for `max_parallel_checks`). The queue immediately dequeued PRs with `Cannot use Merge Queue batch. The 'Merge Queue Batch' feature requires a higher subscription tier`.
+- **PR #1321** reverted both knobs to 1 and moved Greptile into Mergify conditions to restore stable merging.
+- **PR #1336** narrowed the config to a single-block `queue_conditions` setup, locking the queue into in-place mode.
+- The institutional takeaway became "both knobs are paywalled on OSS." Filed in private memory, propagated into other repos.
+
+A second concern reinforced the same conservative config: Greptile silent-skips bot-owned branches (`mergify/merge-queue/*` draft PRs). With only `queue_conditions` (no `merge_conditions`), the team was implicitly running in-place mode and never observed the draft-PR + Greptile interaction. The fear was that adding draft PRs would silently bypass Greptile.
+
+Mergify support clarified on 2026-05-19:
+
+- `batch_size > 1` is the paid "Merge Queue Batch" feature, which groups multiple PRs into one queue position.
+- `max_parallel_checks > 1` is free. It lets Mergify check multiple PRs in parallel via individual `mergify/merge-queue/*` draft PRs — one PR per draft, no batching.
+- Greptile not posting on bot branches is expected. The human-review gate belongs on the **source** PR before queue entry, not on the speculative draft.
+
+PR #1668 codified the corrected configuration.
+
+## Guidance
+
+Treat `batch_size` and `max_parallel_checks` as two independent decisions:
+
+1. **`batch_size`**: keep at `1` on the OSS plan. Any value > 1 trips the paid-tier dequeue with `Cannot use Merge Queue batch`.
+2. **`max_parallel_checks`**: free to raise above 1. Pick a value that matches realistic queue depth; 3 is a reasonable starting point for a small-team repo. Mergify's default has been 5 since June 2025, so a missing setting silently inherits that default.
+
+Adding a `merge_conditions` block (distinct from `queue_conditions`) is what flips Mergify into **draft_pr** mode, which is what makes parallel speculation actually parallelize across PRs. With only `queue_conditions`, the queue runs in-place and `max_parallel_checks > 1` has nothing to parallelize.
+
+When you adopt draft_pr mode, structure gating across **three blocks**, each enforcing the right conditions against the right PR:
+
+- **`queue_conditions`** — gates entry on the *source* PR. Put human-review signals here: review-thread resolution, code-review tools (Greptile), and all CI checks.
+- **`merge_conditions`** — gates merge after the *draft* PR's CI passes. Put *only* the CI checks that run on bot branches here. Do **not** put Greptile or review-thread counts here — Greptile silent-skips bot branches, and draft PRs have no review threads.
+- **`merge_protections`** — re-enforces source-PR conditions before the final GitHub merge. Belt-and-suspenders: ensures speculation can't bypass the human-review gate.
+
+## Why This Matters
+
+The corrected config gives free throughput we were leaving on the floor. With `max_parallel_checks: 1`, queued PRs serialize behind each other's full CI run. With `max_parallel_checks: 3`, three PRs speculate in parallel via independent draft branches; the first one to land merges immediately, and the others either land in sequence (if still applicable to the new `main`) or re-queue with one less position ahead.
+
+Three non-obvious failure modes to avoid:
+
+- **Raising `max_parallel_checks` without adding `merge_conditions`** — nothing breaks, but nothing speeds up either. Mergify stays in in-place mode and parallel checks have no draft PRs to run against. The change appears to take effect in config review and quietly does nothing.
+- **Putting Greptile or `#review-threads-unresolved = 0` in `merge_conditions`** — the queue stalls forever waiting for signals that will never appear on bot branches. The dequeue message is unhelpful; the symptom looks like Mergify is broken.
+- **Putting `pr-title` (or any title-pattern-skipping workflow) in `merge_conditions`** — if the workflow's skip condition was written for a *batch-mode* draft title prefix (e.g. `startsWith(title, 'merge queue:')`), it won't match `draft_pr`-mode draft titles, and the semantic check may fail on every draft. The source PR's title is already validated at queue entry and cannot change while queued, so the re-check is redundant — omit it from `merge_conditions` rather than try to extend the skip pattern.
+
+## When to Apply
+
+Apply this guidance when **all** of the following hold:
+
+- The repo is on Mergify's free OSS plan (or any plan where batch is paywalled but parallel-check isn't — verify against current Mergify pricing).
+- Queue depth regularly exceeds 1 (i.e., PRs wait behind each other's CI).
+- CI runs are long enough that serialization is a real cost.
+- A code-review tool (Greptile, Coderabbit, similar) silent-skips bot-owned branches.
+
+Skip this if queue depth is reliably 0–1 PRs (no parallelism to extract) or if your code-review tool *does* run on bot branches (the `merge_conditions` split is less critical, though still cleaner).
+
+## Examples
+
+**Before** — in-place mode, max parallelism of 1:
+
+```yaml
+queue_rules:
+ - name: default
+ queue_conditions:
+ - base = main
+ - "#review-threads-unresolved = 0"
+ - check-success = build-and-test
+ - check-success = generated-test
+ - or:
+ - check-success = Greptile Review
+ - check-neutral = Greptile Review
+ - check-skipped = Greptile Review
+ batch_size: 1
+ merge_method: squash
+
+merge_queue:
+ max_parallel_checks: 1
+```
+
+**After** — draft_pr mode, parallel speculation, three-tier gating:
+
+```yaml
+queue_rules:
+ - name: default
+ # Source PR gating: human-review signals + all CI checks.
+ queue_conditions:
+ - base = main
+ - "#review-threads-unresolved = 0"
+ - check-success = build-and-test
+ - check-success = generated-test
+ - or:
+ - check-success = Greptile Review
+ - check-neutral = Greptile Review
+ - check-skipped = Greptile Review
+
+ # Draft PR gating: only CI checks that run on bot branches.
+ # No Greptile (silent-skips bots), no review-thread count (drafts have none).
+ merge_conditions:
+ - check-success = build-and-test
+ - check-success = generated-test
+
+ batch_size: 1 # paid above this; keep at 1
+ merge_method: squash
+ update_method: merge
+
+merge_queue:
+ max_parallel_checks: 3 # free; raised from 1
+
+# Re-enforce source-PR gates before the actual GitHub merge.
+merge_protections:
+ - name: require-ready-label-and-ci
+ success_conditions:
+ - or:
+ - label = ready-to-merge
+ # release-please carve-out elided
+ - "#review-threads-unresolved = 0"
+ - check-success = build-and-test
+ - check-success = generated-test
+ - or:
+ - check-success = Greptile Review
+ - check-neutral = Greptile Review
+ - check-skipped = Greptile Review
+```
+
+## Three rules to remember
+
+1. **`batch_size > 1` requires paid Mergify; `max_parallel_checks > 1` does not.** Different features, only the former is paywalled on OSS.
+2. **`merge_conditions` is the switch that turns on draft_pr mode.** Without it, `max_parallel_checks > 1` has nothing to parallelize.
+3. **Greptile silent-skips bot branches by design.** Put Greptile gates in `queue_conditions` and `merge_protections` (both evaluate the source PR), never in `merge_conditions` (evaluates the draft branch).
+
+## Related
+
+- PR #1306 — the regression: raised `batch_size` and `speculative_checks` together, hit the paid-tier dequeue.
+- PR #1321 — the over-correction: reverted both knobs, calcified the "both paywalled" belief.
+- PR #1336 — narrowed to single-block in-place mode.
+- PR #1668 — the fix: split gating into three blocks, raised `max_parallel_checks` to 3, kept `batch_size` at 1.
← 81f07e53 feat(skills): check public library for existing CLI before g
·
back to Cli Printing Press
·
fix(ci): allow queue drafts to skip Greptile 96bc822e →