Ship / Show / Ask: A Lighter PR Review Process
Campaign Delivery's branching + review strategy, adapted from Martin Fowler's
Ship/Show/Ask and
piloted on ad-suite since June 2026. Every PR still goes through a pull
request — that stays our record of what changed and why — but the label
on it decides how much review has to happen before it merges, instead of
every PR defaulting to the same review weight regardless of risk.
The problem it solves
Before this, every PR waited on review the same way, whether it was a one-line config fix or a new integration with a third-party API. Small, obviously-safe changes sat in queues for hours or days next to changes that actually needed scrutiny — reviewer attention is a shared, limited resource, and treating every PR as equally risky spends it on the wrong things.
The three levels
| Label | Meaning | Use when |
|---|---|---|
| Ship | No review needed. Merge as soon as other required checks pass. | Established patterns, routine, low blast radius — docs, config, a small self-contained fix. |
| Show | No need to wait on review; merge, feedback comes after. | Correct and low-risk but worth visibility — a refactor, an interesting fix, a new-ish pattern. |
| Ask | Holds for review before merge. | Migrations/schema changes, auth/authorization, CI/infra changes, a new architectural pattern, or external side effects (creating records, sending notifications, third-party writes) — or just when you're unsure. |
The level is about the change's actual risk, not its Conventional Commits
type — a feat can be a Ship, a fix can be an Ask. When torn between two
levels, pick the higher one.
Merging itself is always a separate, explicit action for every level — nothing here auto-merges. Ship/Show just means nothing is blocking the merge; someone still has to click the button.
What it's actually bought Campaign Delivery
- A PR that used to take ~2.5 days to get approved — the data point that made the case for piloting this in the first place — is exactly the kind of change that should be a Ship.
- A backfill Airflow DAG went from decision doc to production in about a week using Ship/Show, with review happening in parallel instead of gating each step: "I can imagine that if [it] had actually [been] reviewed very diligently, we would be still here hoping it would be merged."
- Zero incidents traced back to a Ship/Show PR that should have been an Ask — the auto-Ask triggers (migrations, auth, infra, external side effects) have held so far.
How this actually gets enforced (not just convention)
Labels alone are a convention — nothing stops someone from merging an
unreviewed Ask PR by accident, or on purpose. Since Sep 2026, ad-suite
enforces the Ask half with two small, portable pieces:
- A CODEOWNERS catch-all (
.github/CODEOWNERS):* @your-teamat the top of the file (must be first — CODEOWNERS is last-match-wins, so a catch-all placed after more specific rules would silently override them). This gives GitHub someone to suggest as a reviewer on everything; it doesn't by itself require anyone's approval. - A label-gated required status check
(
.github/workflows/check-ask-label.yml): fails when a PR carries theAsklabel and has no approving review from someone other than the author, or has an unresolvedCHANGES_REQUESTED.Ship,Show, and unlabeled PRs pass immediately — this check has zero effect on them. Added to the branch ruleset's required status checks so it actually blocks merge, not just reports red.
Why not GitHub's native "require review from code owners" setting:
that rule applies to every PR uniformly — there's no way to scope it to
just Ask-labeled PRs. Turning it on would force a second reviewer onto
every Ship PR too, defeating the entire point of the label. A label-gated
Actions check is the only mechanism that can express "review only when
Ask." Full reasoning: ad-suite decision record
013.
Known limitation, left open on purpose: repo admins carry a standing bypass on most branch rulesets, so this check is binding for the team but an honor-system gate for admins. Worth knowing before you assume it's airtight.
Bringing this to your team
- Create the three labels —
Ship,Show,Ask— and agree on the auto-Ask triggers for your repo (start from the list above; migrations and auth are close to universal, infra paths will differ per repo). - Add a CODEOWNERS catch-all for your team, following the last-match- wins ordering note above if you already have more specific rules.
- Copy
check-ask-label.ymlfromad-suite's.github/workflows/— it's self-contained (no dependency on a shared reusable workflow), so it drops into any repo as-is. - Add the check to your branch ruleset's required status checks. It has to have reported at least once before GitHub will let you select it as required — open one throwaway PR first if needed.
- Write down the convention for your repo — see
ad-suite'sdocs/ship-show-ask.mdas a template for what contributors need to know.
Reference implementation
- ad-suite #958 — CODEOWNERS catch-all
- ad-suite #835 — the enforcement check
- ad-suite #959 — decision record 013 (why this approach, not native code-owner-review)
- ad-suite #960 — contributor-facing process doc
Questions: ask in #team-engineers or reach out to Ben Giese (Campaign
Delivery), who piloted and maintains this on ad-suite.