Skip to content

Draft Pull Requests: Signalling Work in Progress

Lesson 3 of 12Beginner → Intermediate8 min readGitHub Engineering · Pull RequestsVerified: gh 2.98.0, August 2026

A draft pull request is an ordinary pull request with one flag set: it is marked as not ready, and GitHub refuses to merge it.

Everything else works. Checks run. People can comment and review. The diff renders. It links to Issues. The only differences are that the merge button is disabled and automatic reviewer routing does not fire.

Before drafts existed, people signalled the same thing by writing WIP: in the title. That worked socially and not at all mechanically: nothing stopped a merge, nothing knew the difference, and any automation had to parse titles for a convention that varied between teams.

Draft state makes readiness a property of the pull request rather than a note in its title. Because it is structured data, tooling can act on it — merge automation can skip drafts, dashboards can exclude them, and reviewer routing can wait.

Terminal window
gh pr create --draft --title "Spike: alternative retry strategy" \
--body "Exploring approach B. Not for merge — looking for direction on the interface."
DraftReady
Checks runYesYes
Can be reviewedYesYes
Can be mergedNoYes
CODEOWNERS review requested automaticallyNoYes
Counts toward required reviewsReviews are recorded but merging is blocked regardlessYes
Visible in the pull request listYes, marked as draftYes

The CODEOWNERS row is the one that catches people. Marking a pull request ready is what triggers automatic review requests, so a draft that sits for a week has not been silently ignored by code owners — they were never asked.

Early direction-seeking. You have an approach and want to know whether it is the right one before polishing it. Opening a draft with a clear question in the body is far more efficient than describing the approach in chat.

Long-running work that should be visible. A change spanning several days benefits from being visible — colleagues can see it exists, avoid duplicating it, and notice conflicts with their own work early.

Waiting on something external. The code is finished but depends on a dependency release, another pull request, or a decision. Draft state says “not yet” without implying “not done”.

Exercising CI on unfinished work. Checks run on drafts, so you get the pipeline’s opinion while still working.

Stacked changes. When a change depends on another pull request, keeping the dependent one as a draft until its base merges prevents accidental early merging.

As a permanent state. A draft open for three weeks with no activity is not signalling progress; it is a stalled change occupying attention. Either finish it, close it, or say what it is waiting for.

To avoid review. If you want feedback, ask for it explicitly — draft state suppresses automatic routing, so a draft with no direct request usually gets no review at all.

For everything, reflexively. Opening every pull request as a draft and converting immediately adds a step that signals nothing. The flag only carries meaning if it is sometimes absent.

Terminal window
gh pr create --draft --fill
gh pr ready PULL_NUMBER
gh pr list --draft
gh pr view PULL_NUMBER --json isDraft --jq .isDraft

gh pr ready converts to ready for review, which is the moment CODEOWNERS routing fires. Converting back to draft is also possible:

Terminal window
gh pr ready PULL_NUMBER --undo

Converting back is useful when review reveals the change needs substantial rework — it is a clearer signal than leaving it open with unaddressed change requests.

Because draft state is structured, automation should respect it. Two patterns worth knowing:

Skip drafts in merge automation. Any bot that merges approved pull requests must check isDraft, otherwise it will attempt merges GitHub rejects and generate noise.

Terminal window
gh pr list --json number,isDraft,reviewDecision \
--jq '[.[] | select(.isDraft | not) | select(.reviewDecision == "APPROVED") | .number]'

Save CI on drafts, carefully. Some teams skip expensive test suites on drafts to save runner time. This is a real saving and a real trade: the checks then first run at the moment the pull request is marked ready, which is exactly when people want to merge. If you do it, run the fast checks on drafts and reserve only the slow ones.

Draft state means whatever your team decides it means, so decide:

  • Does a draft mean “do not review” or “review the direction, not the details”? Both are reasonable; ambiguity is not.
  • How long may a draft stay open? A soft limit prevents accumulation.
  • Who marks it ready? Normally the author, but on some teams a reviewer does after a design pass.
  • Do drafts run the full pipeline? Decide once rather than per pull request.

Draft state is what makes stacked pull requests workable.

A stack is a sequence where each pull request targets the previous one’s branch rather than main:

main
└── refactor-storage-interface PR #1 → main
└── add-retry-logic PR #2 → refactor-storage-interface
└── expose-retry-config PR #3 → add-retry-logic

Each is reviewable on its own — a reviewer sees only that step’s changes rather than a combined diff. The risk is merging out of order, which produces a confusing history and occasionally a broken build.

Keeping #2 and #3 as drafts until their base merges removes that risk mechanically. When #1 merges, GitHub retargets #2 to main automatically, and you mark it ready.

Two rules keep a stack manageable. Each pull request must be independently correct — a stack where #1 breaks the build until #3 lands is one change split across three pages, not three changes. And keep it shallow: past three or four, rebasing after each merge costs more than the review clarity is worth.

Draft state changes who hears about the pull request, which is the part people find surprising.

Opening a draft does not request review from code owners. Nobody is notified except watchers of the repository. That is the point — but it means a draft opened with no explicit request and no comment is genuinely invisible to the people who might help.

If you want eyes on a draft, ask directly:

Terminal window
gh pr create --draft --title "Spike: alternative retry strategy" \
--body "Exploring approach B — not for merge. @alice, does the interface look right to you?"

Marking ready is the moment routing fires, which has a practical consequence: a draft that sat for a week and is then marked ready enters the review queue as new work, with no credit for the time it spent open. If you want review before it is finished, ask; do not wait for the system to ask for you.

Draft state means whatever your team decides, so decide explicitly. Four questions cover it:

What does a draft mean here? “Do not review” and “review the direction, not the details” are both reasonable and produce different behaviour.

How long may one stay open? A soft limit — a week, two weeks — prevents accumulation. Enforce it by asking, not by closing.

Who marks it ready? Usually the author. On some teams a reviewer does after a design pass, which makes the transition a shared decision.

Does CI run fully on drafts? Skipping expensive suites saves runner minutes and moves the first real signal to the moment someone wants to merge. Running everything costs more and surfaces problems earlier.

Write the answers in CONTRIBUTING.md. Two sentences prevent the ambiguity where one person’s draft means “please look” and another’s means “leave me alone”.

Using a draft to avoid feedback. It suppresses automatic review requests, so this works — badly.

Leaving drafts open indefinitely. They stop signalling anything.

Not explaining why it is a draft. The badge says “not ready”; only you can say why.

Automation that ignores isDraft. Produces failed merge attempts and noise.

Assuming code owners have seen it. They have not been asked until it is marked ready.

Marking ready before checks pass. Wastes a reviewer’s attention on something that will change.

  1. Open a draft pull request with gh pr create --draft, with a body stating what would make it ready.
  2. Confirm checks still run with gh pr checks.
  3. Attempt gh pr merge and observe that it is refused.
  4. Run gh pr ready and note whether any review requests appear that were not there before.
  5. Convert back with gh pr ready --undo and confirm the state changed.
  6. List drafts with gh pr list --draft.

Step 4 is the one to watch if the repository has a CODEOWNERS file — the routing firing at that moment is the behaviour most people are unaware of.

  • A draft is an ordinary pull request with merging disabled and automatic review routing suppressed.
  • Checks run on drafts, so CI feedback is available throughout.
  • Marking ready is what triggers CODEOWNERS review requests.
  • Drafts suit direction-seeking, long-running work and external dependencies; they suit permanent residence badly.
  • Automation must check isDraft or it will attempt merges that cannot succeed.
  • The flag only carries meaning if your team agrees what it means.

Whether to run the full pipeline on drafts is a real trade rather than an obvious call.

Running everything gives the author complete feedback from the first push, catches integration problems while the work is still malleable, and costs runner minutes on code that will change repeatedly.

Running only fast checks saves minutes and moves the first real signal to the moment the pull request is marked ready — which is exactly when someone wants to merge it, and the worst moment to discover a failure.

A middle position works well: run linting and unit tests on drafts, reserve integration and end-to-end suites for ready pull requests.

jobs:
quick:
runs-on: ubuntu-latest
steps: [...]
full:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps: [...]

The draft == false condition is the whole mechanism. It is worth knowing that this makes the full suite’s first run happen at the ready transition — so if it is slow, the transition from draft to ready is where people will feel it.

Auto-merge and draft state interact in a way worth knowing, because the combination is genuinely useful and slightly surprising.

Auto-merge can be enabled on a draft. It will not merge while the pull request remains a draft, because draft is itself a blocking condition. Marking it ready then lets the normal requirements apply, and it merges when they are satisfied.

Terminal window
gh pr merge 128 --auto --squash --delete-branch # queued, not merged
gh pr ready 128 # now eligible

That sequence is a reasonable pattern for work you want to land unattended once it is finished and green: enable auto-merge while still drafting, mark ready when done, and the merge happens without another visit.

The failure mode to be aware of is the same combination used carelessly. Auto-merge on a draft that someone else marks ready will merge without the author returning — which is fine if that was the intent and surprising if it was not. On a shared repository, saying so in the description costs nothing.

Terminal window
gh pr view 128 --json isDraft,autoMergeRequest \
--jq '{draft: .isDraft, autoMerge: (.autoMergeRequest.mergeMethod // "off")}'

Checking both together is the way to see the actual state, since neither field alone tells you what will happen.

Professional ToolkitCODEOWNERS, pull request and issue templates, and repository configuration checklists ready to adapt.