Skip to main content

Overview

A pull request (PR) is the mechanism for proposing changes to a repository and collaborating on them through activities such as review. Changes are proposed through branches: a PR is opened from the head branch, which contains what to apply, into the base branch, which is where the changes are applied, so that only approved changes are merged.

Commit messages

A commit message is a summary of the changes contained in a commit. Standardizing how messages are written as a team convention makes it possible to trace, in a consistent format, which commit in the history changed what. The Conventional Commits format is widely used for this convention: <type>: <summary>, with a prefix that indicates the kind of change. Semantic Versioning is sometimes used together with Conventional Commits. A version takes the form MAJOR.MINOR.PATCH, where the version level to bump is decided by the compatibility of the change. It is sometimes adopted in development where compatibility must be managed, such as libraries and APIs. Combining the two makes the change content easier to understand from the commit message and lets the release version be determined mechanically. When combining the two, prefix the type with the version level, as in <level>-<type>: <summary>. This makes explicit the version level that the type alone does not determine: a feature addition with a breaking change becomes major-feat, and a small feature addition that does not affect compatibility becomes patch-feat. A commit message can consist of the one-line <type>: <summary> alone. To explain the background or reason for a change, add a body — and footers as needed — after a blank line.

Titles

A PR title is a one-line summary of the change. Titles appear in PR lists, notifications, and the history after merging, so standardize a style that lets readers identify the change without opening the description. Base the title on the commit messages, using the same Conventional Commits and Semantic Versioning formats. For a PR with a single commit, reuse the commit message as the title; for a PR with multiple commits, choose the type and summary that represent the change as a whole.

Template

A PR template is a Markdown skeleton that is automatically inserted into the description field when a PR is created. With a template in place, the structure of the description — overview, changes, verification — stays consistent across PRs, and the information reviewers need is codified as a rule in advance.
1

Create the template file

Add .github/PULL_REQUEST_TEMPLATE.md to the repository.
2

Write the skeleton

Lay out the information reviewers need, separated by headings and comments.
3

Merge it into the default branch

The template takes effect only once it exists on the default branch. PRs created after the merge get the skeleton inserted into their description automatically.
Review the template regularly. Remove sections nobody fills in. If there are items that come up often as questions in review, add them as sections.

Draft

A draft is a PR state that signals the work is not yet ready for review. While a PR is a draft it cannot be merged, so unfinished changes are never pulled in by accident. Opening a PR as a draft before the work is finished lets you gather feedback on the direction of the implementation and design early, over the diff of the actual code. Unlike aligning through documents or conversation, the discussion is grounded in working code, so you and the reviewers reach a shared understanding at an early stage. Once the feedback you need is in and the understanding is aligned, you can either continue working on the same PR, or close the draft and open a new PR from scratch. The cost of changing direction grows as the work progresses, so a draft is an effective way to turn back early and keep the sunk cost to a minimum. Select “Create draft pull request” when opening the PR, and switch it out of the draft state with “Ready for review” once it is ready.

Granularity

Pull request granularity is whether a single PR concentrates on one thing — the uniformity of its change content. It is measured by whether the changes are uniform, not by the number of lines or files in the diff.

The difference between size and granularity

Size (lines and files changed) and granularity (uniformity of the change content) are different measures, and granularity is the one that drives the split decision. The test is whether the PR’s change content is uniform — the PR concentrates on one thing — not how large the diff is.

Why granularity matters

A focused diff narrows the scope that has to be reviewed. For the same total amount of work, reviewing one change ten times is lighter for both the author and the reviewer than reviewing ten changes in one PR.

Faster review

A diff with uniform change content fits in a reviewer’s head, so review stops being deferred and finishes in minutes.

Clean reverts

Reverting a PR with a single, uniform change rolls back exactly that change and nothing else.

Fewer conflicts

Short-lived branches are less likely to run into conflicts.

Faster root-cause

A PR with uniform change content also has a narrow impact area, so isolating the cause during an incident is fast.
A large, mixed PR produces a negative cycle: it raises the reviewer’s cognitive load, review is deferred, the branch lives longer, conflicts accumulate, and the next review is harder still. Granularity also matters for context management when AI agents do the work. Having an agent handle multiple requirements or unrelated changes in the same context spreads its attention and lowers precision. Splitting the work into PR units with uniform change content lets the agent focus its limited context on one thing, raising the accuracy of both implementation and review.

Tips for getting granularity right

Break work down first

Decompose the work into independently workable tasks before writing code, so each task maps to one PR. See Task Breakdown.

When in doubt, split smaller

When unsure about the right granularity, choose the smaller one. Discussing granularity within the review exchange aligns expectations with the reviewer. Splitting a PR that has already grown large is harder than starting small and coarsening the granularity later. When in doubt, ship small and flesh the work out incrementally.

Review first

Finer granularity creates more moments where the next piece of work cannot start until a merge lands. If review stalls there, it becomes the bottleneck and slows down overall development speed. So when a review request arrives, handle it before your own work. The context switch of interrupting your own work may be a concern, but reviewing a well-granulated PR takes minutes — sometimes under a minute — so the cost of the interruption is small. Keeping granularity right is what lets the review-first habit take root in the team. See Code Review for what that review actually checks.

Keep CI fast

Finer granularity means more PRs, and CI runs grow in proportion. If CI stays slow, the accumulated waiting before each merge erodes development efficiency instead. Aim for runs under 10 minutes, and treat anything over 5 minutes as slow enough to improve. For concrete techniques, see Keep CI fast in CI/CD.

Separate deploy from release

Treating the deploy of code and the release of a feature as separate events lets unfinished work merge without waiting for the whole feature to be done, so PRs can keep shipping small. See Release for details.

Stacks

A stack is a technique for splitting a series of dependent changes into small PRs stacked on top of each other. Each PR in the stack contains one focused change, and its base is the branch of the PR directly below it instead of the default branch. Keeping PRs small normally collides with dependencies between tasks. When task B builds on the result of task A, you cannot open B’s PR against the default branch until A’s PR is reviewed and merged, so you either wait or bundle A and B into one large PR. Stacking resolves this: you branch B off A’s branch and open B’s PR against A’s branch, so both stay small and neither blocks the other.

How to proceed with tasks

When tasks depend on each other, there are three ways to proceed: wait, bundle, or stack. The following diagrams compare them as branch trees.

Wait

Task B cannot start until task A’s PR merges. Granularity is preserved, but task B’s branch can only be created after task A merges, so the follow-up work stalls while the review is pending.

Bundle

Instead of waiting, tasks A and B are implemented on a single branch and go into a single PR. Nothing stalls, but the diff mixes two intents and grows large, raising the review burden.

Stack

Branch B off A’s branch and point B’s PR at A’s branch. Both PRs stay small, and B proceeds without waiting for A’s review.

How it works

A stack is a chain of branches, and each PR in the stack is called a layer. The bottom branch is based on the default branch, and every branch above is based on the branch directly below it. Each PR targets the branch it was based on, so its diff shows only the changes of its own layer. Review proceeds independently per layer. A reviewer of PR #2 sees only the service-layer diff, because the model changes below it belong to PR #1. Merging proceeds from the bottom up. When the bottom PR merges, GitHub automatically rebases the remaining branches so that the next PR targets the default branch, and the stack shrinks by one layer. You can also merge the entire stack at once by merging the top PR — every PR below it merges with it — or land only the bottom portion and keep working on the rest. See GitHub Docs for the platform-side behavior. This mechanism gives stacks the following benefits.

Granularity is preserved

Each dependent change stays a single-intent PR instead of being bundled into one large diff.

No waiting on review

You continue the next layer on top of the previous branch while its review is still in progress.

Reviews stay small

Each PR shows only its own layer’s diff, so every review stays within a reviewer’s working memory.

Dependencies are explicit

The base chain records which change builds on which, so the merge order is visible on the PRs themselves.
What needs attention when operating a stack is keeping the chain consistent. When a lower layer changes after review feedback, every branch above it must be rebased onto the updated branch. The stacking technique itself is not specific to GitHub and has long been workable by hand, but then each rebase and base retarget is manual work. GitHub’s native support for stacks automates this as a cascading rebase, triggered server-side from the PR or run locally with the gh stack extension. Beyond this automation, another benefit of running a stack on GitHub is that GitHub itself understands that the series of PRs is linked as a stack. Stacking is for changes that genuinely depend on each other. Independent tasks do not need a stack — open them as separate PRs against the default branch.

Using gh stack

gh stack is a GitHub CLI extension that handles the local workflow for a stack as a unit: it creates and tracks branches in dependency order, keeps them rebased, pushes them, creates and links the PRs, and navigates between layers.
GitHub’s server-side support for stacks is in public preview and rolling out to repositories in stages. If gh stack submit fails on a repository, the rollout may not have reached it yet.
1

Install the extension

2

Build the branch chain

Implement each layer on its own branch, branching each new branch off the previous one, and commit. Then adopt the existing branches into a stack, listing them bottom to top:
To build the stack as you go instead, run gh stack init on the first branch and stack each new layer with gh stack add.
3

Create all PRs at once

This pushes every branch and creates the PRs as drafts, with each base set to the branch below. Mark them ready for review once the descriptions are filled in.
4

Merge from the bottom

Merge the bottom PR first; the PRs above are rebased automatically on GitHub’s side. Merging the top PR merges the entire stack at once. To catch your local branches up after merges — fetch, rebase, push, and stack-state sync in one command — run:

Automating stacks with AI agents

Stacks pair well with AI agent workflows. Once work is broken down into issues with explicit dependencies, an agent can implement the issues in dependency order, stacking one branch per issue, and then create all the PRs in one submit. The human concentrates on reviewing and merging each small layer. An automated workflow takes a parent issue — the output of task breakdown — as its input and proceeds as follows.
1

Read dependencies from the parent issue

Fetch the parent issue’s sub-issues and the dependencies between them. The parent-child issues and dependency settings created during task breakdown become the input of the automation as they are.
2

Convert the dependency graph into stacks

Order the dependency graph so that issues to complete first sit at the bottom, and group the issues connected by dependencies — directly or transitively — into one stack. Groups with no dependency connection between them can be worked on in parallel by multiple agents.For example, suppose the sub-issues have the following dependencies. Arrows point at the issue depended on (the one to complete first).This graph converts into the following two stacks.Issues A through D form one stack because following their dependencies connects them all. Issues B and C have no direct dependency on each other, but they are connected through A and D, so they are stacked serially within the same stack. The group of issues E and F has no dependency connection to A through D, so multiple agents can implement it in parallel.
3

Implement while stacking branches

The agent implements the issues in a stack from the bottom, following the dependency order. Each issue gets its own branch and commits, building a chain where issues and branches correspond one to one. Issues with no dependency on each other, like issues B and C in the example above, can be implemented simultaneously in parallel and then assembled into the chain.
4

Create the PRs in bulk

Once the chain is built, gh stack submit --auto pushes the whole stack and creates the PRs with the base chain configured.
5

Humans review and merge from the bottom

Human work starts here: review each layer’s small diff and merge from the bottom. When a lower PR merges, the ones above are rebased automatically on GitHub’s side, so the stack does not need manual restructuring after every merge. Once every PR in the stack is approved, you can also merge the entire stack at once by merging the top PR.
The quality of this automation is decided by the quality of the task breakdown it consumes. Issues with explicit requirements, completion criteria, and dependencies are the prerequisite for automating stacks.

Code Review

What a reviewer checks once a request lands, and how self-review and clear comments keep the exchange fast.

Test Code

What makes a change verifiable in the first place, and how tests keep a PR reviewable.