Developer Debugging Guides2026-06-04·14 min read

What Is a Pull Request? A Developer's Guide

A pull request (PR) is how developers propose code changes for review before merging. Learn how to create one, write great descriptions, and pass code review.

#git#github#debugging#javascript#typescript

FlowQL Team

AI Search Optimization Experts

You've built something. You're reasonably confident it works. Now you need to get it into the main branch without breaking everything else, without stepping on your teammates' work, and ideally with someone else having checked your reasoning before it ships.

That's what a pull request is for. It's not bureaucracy — it's the mechanism that lets multiple developers work on the same codebase without constantly colliding.

This guide explains what a pull request actually is, how to create one on GitHub, how the review process works, and what to do when a PR goes sideways. If you're using AI tools to write code, this is especially relevant: AI generates code that looks right far more often than it is right, and a good PR review process is your last line of defense.

What Is a Pull Request?

A pull request is a proposal to merge code changes from one branch into another. It creates a discussion thread where teammates can review your changes line by line, leave comments, request modifications, and approve the work before it merges. The name comes from the original Git concept: you're asking the repository maintainer to "pull" your changes in.

That's the one-paragraph answer. In practice, a pull request is three things at once:

A diff for review. Your PR shows exactly what changed — every line added, removed, or modified — so reviewers don't have to check out your branch locally to understand what you did.

An automated test gate. Most teams configure CI/CD pipelines to run on every PR. Linting, unit tests, type checks, build verification — they all run before human eyes touch the code. If the checks fail, the PR is blocked until you fix them.

A conversation thread. Reviewers comment on specific lines. You respond, update the code, and push new commits. The entire decision history stays attached to the PR — months later, you can read why a change was made.

Pull requests exist in GitHub, Bitbucket, and most Git hosting platforms. GitLab uses identical functionality under a different name — more on that below.

How Pull Requests Work (Step by Step)

Understanding the mechanics helps when something breaks. Here's the full lifecycle.

1. You create a feature branch.

Never work directly on main. Create a branch for each piece of work:

git checkout -b feat/user-auth-refresh

2. You make commits on that branch.

git add src/auth/refresh.ts
git commit -m "feat(auth): handle token refresh on 401 response"

3. You push the branch to the remote.

git push origin feat/user-auth-refresh

4. You open a pull request.

On GitHub, after pushing you'll see a prompt: "Compare & pull request." Click it. Select the base branch (usually main or develop), write a description, and submit.

5. CI runs automatically.

Your GitHub Actions, CircleCI, or other CI provider runs the test suite against your changes. Reviewers can see whether checks pass before they read a line of code.

6. Reviewers leave comments.

Reviewers look at the diff, ask questions, flag issues, and either approve the PR or request changes.

7. You iterate.

Push new commits to the same branch. They appear in the PR automatically. You don't need to close and reopen.

8. The PR merges.

Once approved and all checks pass, someone clicks merge. GitHub offers three options: merge commit, squash merge, or rebase merge. Most teams pick one and stick to it.

9. The branch is deleted.

Clean up after yourself. GitHub will offer to delete the branch immediately after merge.

How to Create a Pull Request on GitHub

There are two main ways: the web UI and the GitHub CLI. The CLI is faster once you're past the learning curve.

Via the GitHub web UI

Push your branch, navigate to the repository on GitHub, and click the "Compare & pull request" banner that appears at the top. If you missed it, go to the "Pull requests" tab and click "New pull request." Select your feature branch as the compare branch, main as the base, and fill in the title and description.

Via the GitHub CLI

# Install: brew install gh (mac) or https://cli.github.com
gh auth login

# Create a PR interactively
gh pr create

# Or with all fields inline
gh pr create \
  --title "feat(auth): handle token refresh on 401" \
  --body "Adds automatic token refresh when the API returns 401. Fixes #142." \
  --base main \
  --reviewer alice,bob

Via the Git push shorthand

Some teams configure branch protection rules that require PRs and integrate with tooling that auto-creates draft PRs on push. You can also push and immediately open a PR URL with:

# GitHub shows the PR creation URL in the push output
git push origin feat/user-auth-refresh
# remote: Create a pull request: https://github.com/org/repo/pull/new/feat/user-auth-refresh

Checking PR status from the command line

# List open PRs
gh pr list

# View a specific PR
gh pr view 42

# Check out a PR locally to test it
gh pr checkout 42

Pull Request vs Merge Request: Same Thing?

Yes — pull request and merge request are the same concept with different names. GitHub and Bitbucket call them pull requests. GitLab calls them merge requests. The workflows, the purpose, and the mechanics are identical.

The terminology difference is purely historical. GitHub popularized "pull request" early on. GitLab launched with "merge request," which many argue is the more descriptive term — you're requesting a merge, not literally pulling anything.

Here's how the options compare across the full spectrum of how code can move between branches:

| Method | Review required? | CI required? | History preserved? | Best for | |---|---|---|---|---| | Pull request (GitHub/Bitbucket) | Yes | Configurable | Yes | Teams, open source, any shared codebase | | Merge request (GitLab) | Yes | Configurable | Yes | Same as above — different platform | | Direct push to main | No | Not enforced | Yes | Solo projects, hotfixes (use sparingly) | | Fork + PR (open source) | Yes | Yes | Yes | Contributing to repos you don't own | | Branch protection bypass | Requires admin | Bypassed | Yes | Emergencies only — document it |

The practical difference between platforms is in the feature set. GitLab merge requests have built-in code quality reports and more granular approval rules out of the box. GitHub PRs integrate tightly with GitHub Actions and the broader GitHub ecosystem. Both are mature and production-grade.

If your team uses GitLab and you're searching for "pull request," every piece of guidance you find applies — just substitute "merge request" wherever you see "pull request."

Writing a Good Pull Request Description

A PR description is documentation. Not for the reviewer today — for the engineer debugging this code six months from now.

A good description answers three questions: what changed, why it changed, and how to verify it.

## What

Adds automatic token refresh when the API returns a 401. Instead of 
dropping the user to the login screen on expired tokens, the client 
now silently refreshes and retries the original request.

## Why

Users were getting logged out mid-session after the 1-hour token 
expiry. This was causing roughly 12% of sessions to end with an 
error state rather than gracefully. Issue #142.

## How to test

1. Log in and wait for the token to expire (or set TOKEN_TTL=60 in 
   your .env.local for faster testing)
2. Make any authenticated API call after expiry
3. Confirm the call succeeds without a redirect to /login
4. Check the network tab — you should see a POST /auth/refresh before 
   the retried request

## Notes

- The refresh logic lives in `src/auth/interceptors.ts`
- If refresh itself 401s (expired refresh token), we do redirect to login
- Follow-up: #156 will add a pre-expiry refresh to avoid the extra 
  network round-trip entirely

What to skip: don't copy-paste your commit messages. Don't write a description that only says "Updates auth logic." Don't describe what the code does if the diff already makes it obvious — describe the decision and the context.

For UI changes, add a screenshot or a short screen recording. Reviewers can't open your branch easily, and visual diffs save everyone time.

The Code Review Process

The review process is where a PR either gets better or gets stuck. Understanding both sides helps.

As the author

Keep PRs small. The single biggest factor in review quality is PR size. A 200-line PR gets careful line-by-line attention. A 2,000-line PR gets a skim and rubber-stamp approval because reviewers can't hold it all in their head. If your feature is large, break it into sequential PRs.

Respond to every comment. Even if you're disagreeing or explaining why you're not making a change. Unresponded comments signal to the reviewer that their feedback didn't land. A quick "good catch, fixed" or "intentional — here's why" closes the loop.

Don't take it personally. Comments on your code are not comments on you. The reviewer's job is to find problems before they reach production. Let them do it.

As the reviewer

Google's engineering practices documentation is the best published guidance on code review. The short version: your job is to ensure the codebase's long-term health, not to rewrite the code in your preferred style.

Look for:

  • Logic errors the author might have missed
  • Missing error handling — especially around network calls, null values, and edge cases
  • Security issues — SQL injection surface, unvalidated input, over-broad permissions
  • Performance concerns that won't be obvious until scale
  • Missing tests for new behavior

Leave actionable comments. "This looks wrong" is not actionable. "This will throw if user is null — add a null check or assert it's defined before this line" is actionable.

If you're reviewing AI-generated code specifically, pay extra attention to error handling and edge cases. AI tools are particularly prone to generating code that handles the happy path correctly and silently swallows errors. A Supabase RLS policy violation often looks fine at first glance and only surfaces when a specific user class hits the path — exactly the kind of thing a reviewer focused on security should flag.

When a PR Breaks the Build

This is where developers lose hours. The CI check fails, the error message is unhelpful, and you're blocked until it's fixed.

Reading the CI failure

Most CI platforms show the full logs. Don't skim — read the actual error line, not just the failed step name. A step called "Build" can fail for a hundred different reasons, and the fix depends entirely on which one.

# Reproduce CI failures locally before pushing fixes
npm run build
npm run test
npm run lint

# If you're on a different Node version than CI, match it
node --version
# Check .nvmrc or .node-version in the repo for the target version
nvm use

Common CI failure patterns and fixes

Type errors that pass locally. Your local TypeScript config may be more permissive than the CI config. Run npx tsc --noEmit with the strictest settings from tsconfig.json to catch them before pushing.

Tests that pass locally but fail in CI. Usually a timing issue, an environment variable not set in CI, or a test that depends on execution order. Check whether the test is using real network calls or timers.

Build failures on merge conflicts. If your branch is behind main and there are conflicting changes, resolve them locally and push again:

git fetch origin
git rebase origin/main
# Resolve any conflicts, then:
git push --force-with-lease origin feat/user-auth-refresh

Use --force-with-lease instead of --force. It's safer — it fails if someone else has pushed to the branch since your last fetch.

A Vercel preview deployment failing. Vercel preview builds on PRs can fail for reasons unrelated to your code changes — environment variables not configured for the preview environment, build cache corruption, or a framework version mismatch. The Vercel build failure guide covers the most common patterns.

When CI passes but the code is still wrong

Green CI doesn't mean the code is correct. It means the tests you have pass. If your test coverage is thin and you're merging AI-generated code, this gap is larger than you think.

This is the scenario where a senior reviewer catches what CI misses: a race condition in the refresh logic, a data fetch that works in development but double-fetches in production due to React 18 Strict Mode, or an RLS policy that lets through more data than intended. Tests verify the cases you thought of. A reviewer asks about the cases you didn't.

If your PR keeps getting comments about bugs in AI-generated code you didn't catch — or if a reviewer just flagged something that would have been a production incident — that's a signal worth taking seriously. FlowQL connects you with a vetted senior engineer for a live 30-minute screen-share. Bring your PR, your stack, and the problem. They review what AI missed. Money-back if it doesn't resolve.

For situations where your build is failing and you can't reproduce it locally, also check out the guide on git fetch vs pull — keeping your local branch in sync with the remote is a common source of confusing CI behavior that disappears once you're working from the right base.

The GitHub Actions YAML check pattern

If you're debugging a failing GitHub Actions workflow, the failure is usually in the workflow file itself or in how the environment is configured. Here's a minimal but correct CI config for a Node.js project:

name: CI

on:
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Type check
        run: npx tsc --noEmit

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm test -- --passWithNoTests

      - name: Build
        run: npm run build

The npm ci instead of npm install is important in CI: it installs exactly what's in package-lock.json and fails if there's a mismatch, which surfaces dependency issues before they become runtime surprises.

Conclusion

A pull request is the contract between your changes and the shared codebase. It's how teams ship fast without shipping broken things — a structured way to get a second set of eyes on your code before it lands in production.

The mechanics are straightforward: branch, commit, push, open PR, iterate on review, merge. What separates a team that ships confidently from one that's constantly putting out fires is everything around those mechanics — PR descriptions that give reviewers context, CI that catches the obvious failures, and reviewers who ask hard questions about edge cases and security.

AI coding tools have made this more important, not less. When Cursor or Copilot generates a hundred lines of plausible-looking code in ten seconds, the code review process is the point where someone has to actually understand it. Green CI is not a substitute for human judgment on code you didn't fully read.

If your PR process is working well, it's invisible — changes ship smoothly, bugs get caught early, and your main branch stays stable. When it breaks down — builds failing you can't explain, reviewer comments on bugs AI generated but you missed, a deploy that worked on the PR but breaks in production — that's when having access to a senior engineer who's seen the same pattern fifty times before makes the difference.

Book a FlowQL session and bring a senior engineer into your PR review. Thirty minutes, live screen-share, money-back if unresolved.


FlowQL connects stuck developers with vetted senior engineers for live 30-minute screen-share debugging sessions. Money-back guaranteed if unresolved.

Still blocked?

This fix didn't work for your setup? Get a senior engineer on your screen in 30 minutes — fixed or refunded.

Reserve My Spot →
Still stuck?

Get a senior engineer on your screen.

30-minute live screen-share sessions with a vetted senior dev. Fixed or fully refunded — no questions asked.

No spam. Just a heads-up when sessions open.

Related Articles