Decide whether several finished branches ship as ONE PR or stay separate. Use when holding 2+ green branches before `shipyard pr`. Trades CI cost against revert granularity, urgency, and the diff-coverage gate.
Installs into .claude/skills of the current project.
Are you the author of Pr Batching?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/danielraffel-pr-batching)
---
name: pr-batching
description: Decide whether several finished branches ship as ONE PR or stay separate. Use when holding 2+ green branches before `shipyard pr`. Trades CI cost against revert granularity, urgency, and the diff-coverage gate.
---
# PR batching
**A PR costs ~an hour before it can merge:** a ~25-min local diff-coverage build,
then a Shipyard validation. Validations must run **serially** (concurrent runs
from worktrees sharing one `.git` race on `config.lock`). So *n* related PRs is
*n* **sequential** hours, not *n* parallel ones.
That makes "ship these together?" a real question. This is the checklist.
## Default: combine
If you hold 2+ finished, green, **related** branches — combine. Separate PRs are
what you justify, not the reverse.
## Combine when
- **They share unmerged work.** Check first — you may already have the combined
branch:
```bash
git merge-base --is-ancestor feature/a feature/b && echo "b already contains a"
# siblings: is their common point AHEAD of origin/main?
git rev-list --count origin/main..$(git merge-base feature/a feature/b)
```
A chain *is* the combined PR — open it from the tip. Siblings fold with a
cherry-pick.
- **One depends on the other.** If B only compiles because of A, they were always
one change.
- **Same subsystem.** One context, one adoption note, one revert.
- **A well-tested branch would lift a weak one over the coverage gate.** Coverage
is computed over the **whole PR diff**, so folding in a densely-tested branch
*raises* the ratio. A branch stuck at 68% can clear 75% by merging with a
sibling that ships 20 test cases. This is adding covered lines, not hiding
uncovered ones — legitimate.
- **Fewer chances to re-conflict the version-bump line** on a busy `main`.
## One PR per family per session
A single agent session routinely produces a run of small, related changes to
one manifest or branch family on the same day. Shipped one at a time, each pays
its own PR-head gate and its own merge-group gate; the merged-change average was
4.2 native gate runs per change when sessions did this. The rule:
- **Same session + same manifest or branch family + same day ⇒ one PR per
family.** A session with fourteen such changes ships two to four PRs, not
fourteen.
- Before running `shipyard pr` on the second change of a family, check whether
the first is still open. If it is, and it is not yet in the merge queue, push
the new commit onto that branch instead of opening a sibling PR.
- The pre-push advisor lists the related local branches it found and records
each firing in `${XDG_STATE_HOME:-~/.local/state}/pulp/pr-batch-advice.jsonl`;
that log is how the advice is measured.
**Never fold**, even inside one family:
- anything likely to be ejected (a known-flaky area, a change to a test the
queue is currently failing on) — one ejection costs every entry of the batch;
- anything urgent, especially a fix for a red `main` — it ships alone and jumps;
- a docs-only PR into one that runs the native gate, since the docs PR would then
pay for a build it does not need;
- unrelated subsystems, unfinished work, or a change that may need reverting
alone (see below).
## Keep separate when — any ONE of these
- **One is urgent, the other is gated.** Never make a ready branch hostage to a
blocked one. An hour of CI is cheaper than a day of someone else's blocked work.
- **One is unfinished** — a library with no caller, an unwired command,
uncommitted files. **Never ship half-done work to save a CI run.** This is the
trade that looks tempting and is always wrong.
- **Unrelated subsystems.** Costs revert granularity, muddies the migration note.
- **One is risky enough it might need reverting** — isolate it, or a revert drags
good work out with it.
- **One carries a large untestable surface** (native window hosts, format adapters
needing a real DAW). It drags combined coverage *down* — the inverse of the lift
above. Check which way it cuts.
## Folding siblings
```bash
cd <worktree-of-primary-branch>
git log --oneline <shared-base>..feature/sibling # confirm its OWN commits
git cherry-pick <sha>...
tools/ci/governed-build.sh cmake --build build
ctest --test-dir build --output-on-failure -R '<affected>'
```
Cherry-picks compile and still break behaviour — a change fine against the old
base can violate an invariant the other branch just introduced. Run the suites
**together** before shipping.
## Don't
- **Don't run two heavy builds at once to "save time."** They contend, and the
contention surfaces as *test failures* (shellout tests time out under load),
costing you an investigation into a bug that does not exist.
- **Don't batch to dodge a gate.** Adding covered lines to raise a ratio is fine.
A `PULP_SKIP_*` bypass is not.
## Enforcement
This skill is the *judgment*. `tools/scripts/pr_batch_advisor.py` runs from
`.githooks/pre-push` (advisory, never blocks) so the question gets asked no matter
who is driving — Claude, Codex, a human, `gh pr create`, or `shipyard pr`. A skill
only reaches an agent that reads it; a push reaches everyone.
Silence one push: `PULP_SKIP_PR_BATCH_ADVICE=1`.