DevOps · 35 Days · Week 2 Day 08 — Pull Requests & Code Review
1 / 22
Week 2 · Day 8

Pull Requests & Code Review

PRs are not bureaucracy — they are your team's primary tool for knowledge sharing, bug catching, and building a culture of quality. A good PR process makes teams faster, not slower.

⏱ Duration 60 min
📖 Theory 25 min
🔧 Lab 30 min
❓ Quiz 5 min
Session Overview

What we cover today

01
PR as a Collaboration Tool
Why PRs exist. The right PR size. What value they add beyond just merging code.
02
Writing a Great PR Description
Summary, motivation, changes, testing, screenshots — the anatomy of a PR that gets reviewed fast.
03
Effective Code Review
What to look for, how to comment constructively, review types, and the review checklist.
04
PR Template
Creating .github/PULL_REQUEST_TEMPLATE.md so every PR is consistent.
05
Branch Protection & CODEOWNERS
Enforce PR reviews, required CI checks, and auto-assign reviewers at the repo level.
06
Review Anti-patterns
What kills review culture: rubber-stamping, nitpicking, ghost reviewers, late reviews.
07
🔧 Lab — PR Template + Branch Protection
Add PR template to repo, enable branch protection, submit a complete PR and review it.
Part 1 of 6

The PR as a collaboration tool

What a PR actually does
  • Documents the WHY — six months from now, you can read the PR and understand the reasoning, not just the code
  • Catches bugs early — a second pair of eyes catches what automated tests miss (logic errors, edge cases, security issues)
  • Spreads knowledge — reviewers learn about code they don't own; authors get feedback on their patterns
  • Enables async collaboration — reviewer in Bangalore, author in Dubai — no meeting needed
  • Enforces standards — style, security, naming conventions, architecture patterns
Ideal PR Size
< 400 lines changed — reviewers can actually understand it in 15 minutes.

400–600 lines — borderline. Consider splitting if possible.

> 600 lines — split it. Large PRs get rubber-stamped or ignored. Every bug escaped to production from a 1200-line "mega PR."
What a PR is NOT
  • A blame tool — reviews are about the code, not the author
  • A rubber stamp — "LGTM" on a 1000-line PR helps nobody
  • Bureaucracy to slow you down — a 15-minute review preventing a production bug is worth it
  • A battle — reviewers suggest, authors decide; both can be wrong
💡 The PR-to-Deploy Pipeline
In GitHub Flow + CI/CD:
Open PR → CI runs (tests, lint, security) → Review passes → Merge to main → Deployment auto-triggers.

The PR is the gateway. It's where quality is enforced before code ever reaches production.
Part 2 of 6

Writing a great PR description

Open feat: add health check endpoint with version info
bastian-joe wants to merge 3 commits into main from feat/health-endpoint · opened 2 hours ago · CI ✅
Summary
Adds a /health endpoint returning JSON with service status, version, and timestamp. Required by the load balancer health check config (Day 9 nginx setup).
Motivation
The load balancer currently has no way to verify app health. Without this, a crashed app continues to receive traffic for up to 30 seconds. Closes #12.
Changes
• Added healthcheck.sh script returning status + version + timestamp
• Updated README with health check usage
• Added execute permission
Testing
✅ ./healthcheck.sh returns {"status":"OK"}
✅ exit code 0 on healthy state
☐ Integration test pending
5-Part PR Description
  • Summary — 1–2 sentences. What changed? A reviewer should understand the intent in 10 seconds.
  • Motivation — Why was this needed? Link the issue/ticket: Closes #42. Answers "why are we doing this?"
  • Changes — Bullet list of what was modified. Helps reviewers navigate large diffs.
  • Testing — Checklist of how you tested. Gives reviewers confidence to approve.
  • Screenshots — For UI changes, always include before + after. A picture reviews faster than code.
⚠ Bad PR Description
"fix stuff" — impossible to review. The reviewer must read every line to understand the intent. They will either spend 45 minutes or rubber-stamp it. Both outcomes are bad.
Part 3 of 6

Code review — what to look for

🐛
Bugs & Logic
Off-by-one errors, null pointer risks, race conditions, incorrect edge case handling, wrong algorithm complexity.
"This loop starts at index 1 but the array is 0-indexed — will skip the first item."
🔒
Security
Hardcoded secrets, SQL injection risk, missing auth checks, exposed internal APIs, unvalidated user input.
"This SQL query concatenates user input directly — use parameterised queries instead."
📖
Readability
Function names that don't explain intent, magic numbers, unnecessary complexity, missing comments on tricky logic.
"What does 'x' represent here? Renaming to 'retryCount' would make this self-documenting."
🏗
Architecture
Does this fit the existing patterns? Is the right abstraction used? Is there a simpler approach? Breaking existing contracts?
"This creates a circular dependency between the auth and user modules — consider extracting to a shared module."
Performance
N+1 database queries, missing indexes, unnecessary loops inside loops, loading too much data into memory.
"This calls the DB inside a for loop — this will make N queries for N users. Use a single query with IN clause."
Test Coverage
Are the new paths tested? Are edge cases covered? Does the test actually test what it claims? Are tests readable?
"The error path in the catch block isn't tested — what happens if the API call fails?"
💡 Priority Order
Review in order of impact: Security (no compromise) → Bugs/Logic (prevent production issues) → Architecture (hard to change later) → Performance (measure before optimising) → Readability/Style (important but least critical). Don't block a PR on style when there's a security issue.
Part 3 of 6 — continued

How to comment constructively

Comment Types — Prefix Your Intent
  • nit: — Minor style or naming preference. Non-blocking. Author can ignore.
  • suggestion: — Optional improvement. Not required to merge.
  • question: — Asking for clarification, not requiring change.
  • issue: — Something that must be fixed before merge.
  • blocking: — Critical issue. Merge is blocked until resolved.
  • praise: — Explicitly positive. "Great approach here!"
❌ Bad Comments
  • "This is wrong." — No explanation of why or how to fix.
  • "Why would you do it this way?" — Sounds accusatory.
  • "Change this." — No context, sounds like an order.
  • "LGTM" on a 1000-line PR — Not a real review.
  • Nitpicking style while missing a SQL injection — Wrong priority.
✅ Good Comments
  • nit: "Consider renaming fn to fetchUserById for clarity."
  • suggestion: "Optional: Array.from() would be more readable than [...x] here."
  • issue: "This will throw a NullPointerException if user is null — add a guard clause."
  • blocking: "The DB password is hardcoded on line 47. Must be moved to env var before merge."
  • question: "Is there a reason we're using setTimeout here instead of async/await?"
  • praise: "Smart use of early returns — this is much cleaner than nested ifs."
The Reviewer's Mindset
You're a collaborator, not a gatekeeper. Your job is to help the author ship quality code — not to prove they were wrong. Suggest, don't dictate. Ask, don't assume. Praise as often as you critique.
Part 4 of 6

PR Template — consistent quality by default

.github/PULL_REQUEST_TEMPLATE.md
## Summary


## Motivation & Context



## Type of Change
- [ ] 🐛 Bug fix (non-breaking change that fixes an issue)
- [ ] ✨ New feature (non-breaking change adding functionality)
- [ ] 💥 Breaking change (fix or feature causing existing functionality to break)
- [ ] 📚 Documentation update
- [ ] 🏗 Infrastructure / DevOps change
- [ ] 🔒 Security fix

## Changes Made

-
-

## How Has This Been Tested?
- [ ] Unit tests pass locally (`npm test`)
- [ ] Manual testing steps:
  1. 
  2.
- [ ] No regressions in existing functionality

## Checklist
- [ ] My code follows the project's style guide
- [ ] I have added/updated relevant documentation
- [ ] No secrets, passwords, or API keys in this PR
- [ ] CI pipeline passes (tests + lint + security scan)
- [ ] PR size is under 400 changed lines

## Screenshots (if applicable)
How it works
Create the file at .github/PULL_REQUEST_TEMPLATE.md. Commit and push to main. From now on, every new PR on GitHub will automatically pre-fill the description box with this template. Zero effort for authors — consistent quality by default.
Multiple templates

For different PR types, use a directory:

.github/
  PULL_REQUEST_TEMPLATE/
    feature.md
    bugfix.md
    hotfix.md

Access via URL param: ?template=hotfix.md

💡 Issue Linking Magic
Write Closes #42 in the PR description. When the PR is merged, GitHub automatically closes issue #42. Also works: Fixes #42, Resolves #42. The Kanban card moves to Done automatically.
Part 5 of 6

Branch Protection — enforce your standards

GitHub Settings → Branches → Add Rule (pattern: main)
# === Required Settings ===
✅ Require a pull request before merging
   Required approvals: 1
   Dismiss stale reviews on new commit: ON
   Require review from code owners: ON (if CODEOWNERS exists)

✅ Require status checks before merging
   Search for and add your CI job name
   e.g. "build-and-test" from your GitHub Actions workflow
   Require branches to be up to date: ON

✅ Require conversation resolution before merging
   All review comments must be resolved

✅ Restrict who can push to matching branches
   Only designated team leads for emergency merges

✅ Do not allow bypassing the above settings
   Even repo admins must follow the rules

# === Auto-delete Branches ===
✅ Automatically delete head branches after merge
   No dangling branches after PR is merged

# === Allowed Merge Types (pick one) ===
✅ Allow squash merging  ← Recommended for GitHub Flow
❌ Allow merge commits   ← Disable for clean history
❌ Allow rebase merging  ← Optional
CODEOWNERS

Create .github/CODEOWNERS to auto-request reviews from the right people:

# Global: all files → team lead
*                    @org/team-leads

# Frontend: UI directory
/src/components/     @org/frontend

# Infrastructure: IaC + K8s
/terraform/          @org/platform
/k8s/                @org/platform
/.github/workflows/  @org/platform

# Security-sensitive files
/src/auth/           @org/security-team

When someone opens a PR that touches these paths, the specified team is automatically added as a required reviewer.

💡 Effect on DORA
Branch protection + required CI checks = Change Failure Rate drops dramatically. You can't merge unreviewed code or code that fails tests. This is the automated quality gate before production.
Part 6 of 6

Review anti-patterns — what kills review culture

Rubber Stamping

"LGTM 👍" on a 1200-line PR that was open for 4 hours.

This defeats the entire purpose. The reviewer didn't actually review. A bug shipped in that PR will be blamed on the "reviewer who approved it." Don't be that person.

Fix: Set a PR size limit. Anything over 400 lines requires a genuine review justification.

Style Nitpicking

Blocking a PR because of trailing spaces, import ordering, or naming conventions when automated linters should catch these.

Fix: Automate style checks with Prettier, ESLint, commitlint. Run them in CI. If a linter didn't catch it, it doesn't block the PR. Day 9 covers this.

Ghost Reviewer

Assigned as reviewer. Never reviews. PR waits for days. Author pings twice, no response. PR eventually merges without review to meet the sprint deadline.

Fix: Define SLA — reviews must happen within 4 business hours. Automate reminders. Don't assign busy people as sole reviewer.

Late Design Review

A reviewer comments "the entire architecture here is wrong — we should use event-sourcing instead" on a 3000-line PR after 5 days of work.

Fix: Discuss architecture before coding. Use design documents, RFC (Request for Comments) processes, or a 15-min sync. Save large design feedback for planning, not PR review.

💡 The 4-Hour SLA Rule
Set a team agreement: PRs under 400 lines get reviewed within 4 business hours. This keeps the pipeline flowing and prevents "waiting for review" from becoming your lead time bottleneck.
Hands-On Lab

🔧 PR Template & Branch Protection

Add a PR template, enable branch protection, and submit a complete, well-described PR that you review yourself

⏱ 30 minutes
my-devops-app repo ✓
GitHub account ✓
🔧 Lab — Steps

Build your PR process

1
Add PR Template to the repo
Create .github/PULL_REQUEST_TEMPLATE.md with the template from Slide 7. Commit directly to main: chore: add PR template.
2
Add CODEOWNERS file
Create .github/CODEOWNERS with * @YOUR_GITHUB_USERNAME. This makes you the required reviewer for all files.
3
Enable Branch Protection on main
GitHub → repo → Settings → Branches → Add rule → Pattern: main. Enable: Require PR, dismiss stale reviews, require conversations resolved. Allow Squash only. Save.
4
Create a feature branch + PR using the template
git switch -c feat/add-env-config → create .env.example → commit → push → open PR. Notice the template pre-fills the description. Fill it in fully.
5
Leave inline code review comments
In the PR's Files Changed tab, hover a line → click the + button → leave 2 comments: one nit: and one suggestion:. Resolve them both.
6
Approve + Squash merge
On the PR page click "Review changes" → "Approve" → write a short review summary. Then "Squash and merge". Write a clean squash message using Conventional Commits format.
🔧 Lab — Commands

Complete lab commands

🐧🪟 bash / WSL2
# === 1. Add PR template ===
cd my-devops-app
mkdir -p .github
cat > .github/PULL_REQUEST_TEMPLATE.md << 'EOF'
## Summary
<!-- What does this PR do? -->

## Motivation & Context
<!-- Why is this needed? Closes #ISSUE -->

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation update
- [ ] Infrastructure change

## Changes Made
-

## Testing
- [ ] Tests pass
- [ ] Manual testing done

## Checklist
- [ ] No secrets in this PR
- [ ] CI passes
- [ ] Under 400 lines changed
EOF

git add .github/PULL_REQUEST_TEMPLATE.md
git commit -m "chore: add PR description template"

# === 2. Add CODEOWNERS ===
echo "* @YOUR_GITHUB_USERNAME" > .github/CODEOWNERS
git add .github/CODEOWNERS
git commit -m "chore: add CODEOWNERS for all files"
git push origin main

# === 3. Verify .github/ directory ===
ls .github/
# CODEOWNERS  PULL_REQUEST_TEMPLATE.md
🐧🪟 Feature PR
# === 4. Create feature for the PR ===
git switch main && git pull
git switch -c feat/add-env-config

# Create .env.example (never commit real .env)
cat > .env.example << 'EOF'
# Application configuration
NODE_ENV=development
PORT=8080
LOG_LEVEL=info

# Database (fill these in for local dev)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=appuser
DB_PASS=          # Never commit real passwords!

# External APIs
API_KEY=          # Get from team secrets manager
EOF

git add .env.example
git commit -m "chore: add env config example file"

echo "## Environment Setup" >> README.md
echo "Copy .env.example to .env and fill in values." >> README.md
git add README.md
git commit -m "docs: add environment setup instructions"

git push -u origin feat/add-env-config
# → Open PR on GitHub → template auto-fills!
# → Fill in all sections → Submit
# → Review inline → Add 2 comments → Resolve
# → Approve → Squash and merge
💡 Verify Branch Protection Works
After enabling branch protection, try: git push origin main directly. GitHub should reject with: "error: remote: — You cannot push to a protected branch." This confirms protection is active.
Knowledge Check

Quiz Time

3 questions · 5 minutes · PR size, merge strategies, and squash merging

Test your PR knowledge →
QUESTION 1 OF 3
What is the recommended maximum PR size for fast, effective review?
A
100 lines — extremely tight limit
B
400 lines — reviewable in ~15 minutes
C
1000 lines — one full feature
D
No limit — as long as there is a description
QUESTION 2 OF 3
Which merge strategy produces the cleanest, most linear git history with no merge commits?
A
Merge commit — preserves full branch history
B
Fast-forward merge — only possible if no divergence
C
Squash and merge — one commit per PR on main
D
Rebase and merge — replays each commit linearly
QUESTION 3 OF 3
What does Squash and Merge combine into a single commit on main?
A
All commits made in the last week across all branches
B
All commits on the feature branch being merged
C
All commits from all currently open PRs
D
All commits since the last tagged release
Day 8 — Complete

What you learned today

📝
PR Description
Summary + Motivation + Changes + Testing + Screenshots. <400 lines. "Closes #42" auto-closes issues.
👀
Code Review
Security → Bugs → Architecture → Performance → Style. nit:, issue:, blocking:, praise: prefixes.
📋
PR Template
.github/PULL_REQUEST_TEMPLATE.md auto-fills every new PR. Consistent quality by default.
🛡
Branch Protection
Require PR + CI + conversation resolution. CODEOWNERS auto-assigns reviewers. Squash merges only.
Day 8 Action Items
  1. PR template committed to .github/PULL_REQUEST_TEMPLATE.md
  2. CODEOWNERS file created
  3. Branch protection enabled on main — direct push blocked
  4. Completed PR visible in GitHub with template filled in
Tomorrow — Day 9
Git Hooks & Automation

Husky, lint-staged, commitlint — automate code quality gates locally. Catch style issues, bad commit messages, and failing tests before they reach the CI pipeline.

husky lint-staged commitlint pre-commit
📌 Reference

The reviewer's checklist

Before you start reviewing
  • ☐ Read the PR description — do you understand the intent?
  • ☐ Is the PR under 400 lines? If not, ask for a split.
  • ☐ Is there a linked issue or ticket?
  • ☐ Is the branch up to date with main?
  • ☐ Is CI passing?
Security check first
  • ☐ Any hardcoded secrets, tokens, passwords?
  • ☐ Any user input directly in SQL / shell commands?
  • ☐ Any new endpoints missing authentication?
  • ☐ Any sensitive data being logged?
  • ☐ Any new dependencies with known CVEs?
Logic & correctness
  • ☐ Does the code do what the PR description says?
  • ☐ Are edge cases handled? (null, empty, max values)
  • ☐ Any obvious off-by-one errors?
  • ☐ Are errors caught and handled properly?
  • ☐ Are tests added for the new code paths?
Readability & maintainability
  • ☐ Are functions and variables named clearly?
  • ☐ Is complex logic explained with a comment?
  • ☐ Is there any dead code or commented-out code?
  • ☐ Is the code duplication that could be extracted?
  • ☐ Will a new team member understand this in 6 months?
📌 Reference

PR examples — bad vs good

❌ PR That Will Get Ignored
Title: fix stuff

# (description is empty)

1,247 changed files | +8,432 −3,219

# Reviewer: "LGTM 👍"

Problems: No description. Vague title. 1200+ lines — impossible to review properly. Rubber-stamped. Bugs shipped.

✅ PR That Gets Reviewed Fast
feat: add health check endpoint (Closes #12)

## Summary
Adds /health returning JSON status, version, timestamp.

## Motivation
LB has no health check, crashed app gets traffic for 30s.

## Testing
✅ Manual: ./healthcheck.sh → {status:"OK"}
✅ CI passes

3 files changed | +47 −2

Good: Clear title with issue link. Concise description. Small diff (47 lines). Explains the why. Tested. Reviewable in 5 minutes.

📌 Reference

Issue linking & GitHub automation

Issue Auto-closing Keywords
# In PR description or commit message body:
Closes #42        # Auto-closes issue 42 on merge
Fixes #42         # Same effect
Resolves #42      # Same effect

# Multiple issues:
Closes #42, #43, #44

# Cross-repo:
Closes org/other-repo#42

# In commit messages (also works):
git commit -m "feat: add health check

Closes #12"

# What happens on merge:
# → Issue #42 automatically closed
# → Issue moves to "Done" in GitHub Projects
# → Your Kanban card moves to Done

# GitHub Actions that trigger on PR events:
on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [main]
  pull_request_review:
    types: [submitted]
GitHub Projects Integration

If your Kanban board (from Day 3) is linked to your repo:

  • Create an Issue for each task → it appears as a board card
  • Open a PR that references the issue → card shows "In Review"
  • Merge the PR → issue auto-closes → card moves to Done

Your board is now a live view of your workflow — zero manual card moving needed.

PR Labels — categorise your PRs

Create labels in your repo and add them to PRs:

feature bug infrastructure breaking-change needs-review hotfix

Labels power GitHub search: is:pr is:open label:hotfix

📌 Reference

Code review etiquette guide

Situation ❌ Don't ✅ Do
You spot a bug "This is wrong." "issue: This will throw a NullPointerException if user is null — consider adding a guard clause on line 12."
Minor style preference Block the PR until renamed "nit: consider renaming 'x' to 'retryCount' — not blocking, your call."
You don't understand code "This makes no sense." "question: I'm not familiar with this pattern — could you add a comment explaining why setTimeout is used here instead of a Promise?"
You see good code Stay silent (only comment on problems) "praise: really clean use of early returns here — makes the happy path obvious."
You have an alternative idea "Do it my way." "suggestion: Array.reduce() would be slightly more idiomatic here — happy to discuss which approach fits the codebase style better."
Large architecture change needed List 20 architectural comments in the PR Approve the current PR with a "question:" comment, and open a design discussion separately before the next PR.
You're the author and receive criticism Get defensive or ignore comments Thank for the feedback, address all comments (fix or explain why not), resolve all conversations before requesting re-review.
Week 2 Progress

Week 2 — 4 of 5 days complete

Day Topic Lab Output Status
Day 6 ✅Git Fundamentalsmy-devops-app on GitHub, SSH + aliasesDone
Day 7 ✅Branching StrategiesGitHub Flow PR merged, conflict resolvedDone
Day 8 ✅Pull Requests & Code ReviewPR template + branch protection + reviewed PRDone
Day 9 →Git Hooks & AutomationHusky + lint-staged + commitlint pre-commit hookTomorrow
Day 10Git Advanced Labrebase -i, cherry-pick, bisect, reflogFriday
The Complete Quality Pipeline (so far)
Day 6: Git identity → Day 7: Branching strategy → Day 8: PR template + Branch protection.

Now, every code change must: branch → commit (Conventional) → PR (templated) → review → CI ✅ → squash merge → deploy.

Day 9 adds: commit hooks enforcing this before code even reaches the PR.
Your repo now has
  • .gitignore preventing secret leaks
  • ✅ Conventional Commits on all messages
  • .github/PULL_REQUEST_TEMPLATE.md
  • .github/CODEOWNERS
  • ✅ Branch protection on main
  • ✅ Squash merges only
  • ✅ Multiple closed PRs in history