Pull Request Description Standards
PR Title Conventions
The PR title is the first thing reviewers see. It must communicate the change clearly and concisely.
Format
<type>: <concise description of what changed>
Type Prefixes
Prefix Use When
feat:
Adding new functionality
fix:
Correcting a bug
refactor:
Restructuring without behavior change
docs:
Documentation only
test:
Adding or updating tests
chore:
Dependencies, tooling, config
perf:
Performance improvement
style:
Formatting, whitespace, naming
ci:
CI/CD pipeline changes
revert:
Reverting a previous change
Title Rules
-
Keep under 72 characters
-
Use imperative mood: "Add search endpoint" not "Added search endpoint" or "Adds search endpoint"
-
Do not end with a period
-
Be specific: "fix: resolve login redirect loop on expired sessions" not "fix: login bug"
-
Include ticket number if applicable: "feat: add user search (PROJ-1234)"
Good vs Bad Titles
Bad: "Updates" Good: "feat: add full-text search to user directory"
Bad: "Fix bug" Good: "fix: prevent duplicate charge on payment retry"
Bad: "Refactoring the auth module and also fixing some tests and updating docs" Good: "refactor: extract token validation into dedicated service"
PR Description Template
Every PR should follow a structured description. Use this template as a starting point and adapt to your team's needs.
Standard Template
Summary
A 1-3 sentence overview of what this PR does and why. This should be understandable by someone who has not read the code.
Motivation
Why is this change needed? Link to the issue, bug report, or product requirement that motivated this work.
Closes #123
Changes
A bulleted list of the specific changes made:
- Added
SearchServiceclass with full-text query support - Created
/api/users/searchendpoint with pagination - Added search index migration for the users table
- Updated API documentation with new endpoint
Test Plan
How was this change tested? Be specific.
- Unit tests for
SearchServicecovering empty queries, partial matches, and pagination - Integration test for the search endpoint with test database
- Manual testing: verified search returns expected results in staging
- Load tested with 10k users in the index
Screenshots / Recordings
If the change has a visual component, include before/after screenshots or a screen recording. For API changes, include example request/response pairs.
Before: (screenshot or N/A)
After: (screenshot or N/A)
Notes for Reviewers
Any additional context that will help reviewers:
- I chose cursor-based pagination over offset because [reason]
- The migration is backward-compatible and can be rolled back
- This depends on PR #456 being merged first
Checklist
- Tests pass locally
- Code follows project style guidelines
- Documentation updated if needed
- No secrets or credentials included
- Migration is reversible (if applicable)
- Breaking changes documented (if applicable)
Minimal Template (for Small Changes)
Summary
Fix null pointer exception when user profile has no avatar set.
Closes #789
Changes
- Added null check in
UserProfile.getAvatarUrl() - Added test case for users without avatars
Test Plan
- Unit test added for the null avatar case
- Existing tests pass
Linking Issues
Automatic Issue Closing
Use GitHub keywords in the PR description body to automatically close issues when the PR merges:
Closes #123 Fixes #456 Resolves #789
-
Place these in the Motivation or Summary section
-
Use one keyword per issue, each on its own line for multiple issues
-
These keywords are case-insensitive
Cross-Repository References
Closes org/other-repo#123
Linking Without Closing
When a PR is related to but does not fully resolve an issue:
Related to #123 Part of #456 See also #789
Draft PRs vs Ready-for-Review
When to Open a Draft PR
Open a draft PR when:
-
You want early feedback on your approach before finishing
-
The work is in progress but you want CI to run
-
You are working on a stacked PR that depends on another PR
-
You want to share progress with the team without requesting formal review
-
You need to collaborate with another developer on the branch
Draft PR Etiquette
-
Prefix the title with [WIP] or [Draft] in addition to using GitHub's draft status
-
State what is done and what remains in the description:
Status
Done:
- Database schema and migration
- Repository layer
Remaining:
- Service layer business logic
- API endpoint
- Tests
What I Need Feedback On
-
Is the schema design appropriate for the query patterns we expect?
-
Should we use a separate table for search metadata?
-
Convert to ready-for-review only when all work is complete
-
Do not request specific reviewers on draft PRs unless you need targeted feedback
Converting to Ready
Before converting a draft PR to ready-for-review:
-
All commits are clean and well-organized
-
Description is complete with all template sections filled
-
CI passes
-
Self-review completed (see checklist below)
-
WIP or Draft prefix removed from title
PR Size Guidelines
Target Size
Metric Target Maximum
Lines changed 50-200 400
Files changed 1-8 15
Commits 1-5 10
Review time 15-30 min 60 min
Why Small PRs Matter
-
Faster review turnaround (hours not days)
-
Higher quality reviews (reviewers stay focused)
-
Easier to revert if something breaks
-
Less merge conflict risk
-
Better git history for debugging
Breaking Down Large Changes
When a change exceeds size guidelines, split it into a sequence of PRs:
-
Infrastructure first -- Schema changes, new interfaces, configuration
-
Core logic -- Business logic implementation
-
Integration -- Wiring components together, endpoint creation
-
Polish -- Error handling improvements, logging, documentation
Each PR should be independently mergeable and should not break existing functionality.
Acceptable Large PRs
Some PRs are legitimately large:
-
Generated code (migrations, protobuf output, lock files)
-
Bulk rename or reformatting (use a separate commit or PR)
-
Dependency upgrades with lock file changes
-
Initial project scaffolding
Mark these clearly in the description: "Note: This PR is large because it includes generated migration files. The actual hand-written changes are in src/services/ (~80 lines)."
Stacked PRs
Stacked PRs break a large feature into a chain of dependent PRs that build on each other.
When to Use Stacked PRs
-
A feature requires 500+ lines of changes
-
You want reviewers to focus on one logical layer at a time
-
Multiple developers are working on interconnected changes
Stacked PR Workflow
PR #1: feat: add user search database schema └── PR #2: feat: add search service with query logic └── PR #3: feat: add search API endpoint and docs
Stacked PR Rules
-
Each PR in the stack must work independently if merged alone (no broken states)
-
Number the PRs and cross-reference them:
Stack
This is PR 2 of 3 in the search feature stack:
- #100 - Database schema (merged)
- #101 - Search service (this PR)
- #102 - API endpoint (draft, depends on this PR)
- Base each PR on the previous PR's branch, not on main:
PR 1
git checkout -b feature/search-schema
PR 2 (based on PR 1)
git checkout -b feature/search-service feature/search-schema
PR 3 (based on PR 2)
git checkout -b feature/search-endpoint feature/search-service
-
When an earlier PR is merged, rebase subsequent PRs onto main
-
Review and merge in order -- do not merge PR 3 before PR 1
Updating Stacked PRs After Review
When you need to update PR 1 based on review feedback:
Make changes on PR 1's branch
git checkout feature/search-schema
... make changes, commit ...
Rebase PR 2 onto updated PR 1
git checkout feature/search-service git rebase feature/search-schema
Rebase PR 3 onto updated PR 2
git checkout feature/search-endpoint git rebase feature/search-service
Force-push all updated branches
git push --force-with-lease origin feature/search-schema git push --force-with-lease origin feature/search-service git push --force-with-lease origin feature/search-endpoint
Self-Review Checklist
Before requesting review, go through the PR yourself as if you were the reviewer.
Code Quality
-
No commented-out code left behind
-
No debug logging (console.log, print statements) unless intentional
-
No TODO comments without associated issue numbers
-
Variable and function names are clear and consistent
-
No unnecessary complexity -- could this be simpler?
-
No duplicated logic that should be extracted
Correctness
-
Edge cases handled (null, empty, boundary values)
-
Error cases handled with appropriate messages
-
No race conditions in concurrent code
-
Database queries are efficient (no N+1, proper indexes)
-
Feature flags used for incomplete or experimental features
Security
-
No secrets, API keys, or credentials in the code
-
User input is validated and sanitized
-
Authentication and authorization checks present where needed
-
SQL injection, XSS, and CSRF protections in place
-
Sensitive data is not logged
Testing
-
New code has corresponding tests
-
Tests cover happy path and error cases
-
Tests are deterministic (no flaky tests)
-
Test names clearly describe what they verify
Documentation
-
Public APIs have documentation
-
Complex logic has explanatory comments
-
README updated if setup steps changed
-
CHANGELOG updated for user-facing changes
-
API documentation updated for endpoint changes
Diff Review
-
Reviewed the full diff on GitHub before requesting review
-
No unintended file changes (formatting, unrelated fixes)
-
Commit history is clean and logical
-
No merge commits from pulling main (rebase instead)
Responding to Review Feedback
Etiquette
-
Respond to every comment, even if just with "Done" or "Fixed"
-
If you disagree, explain your reasoning respectfully and propose alternatives
-
Do not resolve comments yourself unless your team convention allows it -- let the reviewer resolve their own comments
-
If a discussion becomes lengthy, move it to a call and summarize the outcome in the PR
-
Push review fixes as new commits during review, squash before merge
Handling Requested Changes
Review Response
- [Comment about error handling] Fixed -- added try/catch with proper logging in abc123
- [Comment about naming] Renamed
processDatatovalidateAndStorePaymentin def456 - [Comment about test coverage] Added edge case tests for empty input in ghi789
- [Comment about architecture] I'd prefer to keep this in a single service for now -- the traffic doesn't justify the complexity of splitting. Happy to discuss further.
Example: Well-Written PR
feat: add cursor-based pagination to user search endpoint
Summary
Replaces the offset-based pagination on /api/users/search with cursor-based
pagination to handle our growing user base without performance degradation.
Motivation
The user directory now has 2M+ records. Offset pagination causes increasingly slow queries at high page numbers because the database must scan and discard rows. Cursor-based pagination maintains constant performance regardless of position.
Closes #1234
Changes
- Replaced
pageandper_pageparams withcursorandlimit - Added
next_cursorandhas_moreto response envelope - Updated
UserRepository.search()to use keyset pagination on(created_at, id) - Added database index on
(created_at, id)for the users table - Marked old
pageparameter as deprecated with warning header - Updated API docs with new pagination format
Test Plan
- Unit tests for cursor encoding/decoding
- Integration tests verifying correct page traversal over 1000 records
- Verified backward compatibility: requests with
pageparam still work (with deprecation warning) - Load test: p99 latency at page 10000 dropped from 2.3s to 45ms
Notes for Reviewers
- The cursor is a base64-encoded JSON of
{created_at, id}-- intentionally opaque to clients - Old
pageparameter will be removed in v3.0 (tracked in #1235) - Migration
20240115_add_users_pagination_index.sqlis safe to run on production without downtime