Chapter 4: Git Workflows & PR Automation
Learning Objectives
- Let Claude handle your entire Git workflow: branch, code, commit, PR — without typing git commands yourself
- Build a real feature (dark mode toggle) with proper branching and atomic commits
- Use Git Worktree for true parallel development across multiple Claude sessions
- Break things on purpose and recover with
/rewind— building confidence to try bold changes - Use pipe mode for instant code review before every merge
Appendix links: A05 Tool Calling Internals explains how Claude selects between Read and Bash when it needs git information. A01 Prompt Engineering covers how to write prompts that produce good commit messages.
Concepts
Claude's Git Integration Goes Deep
Claude Code doesn't just run git commands. It understands your changes, writes commit messages by analyzing diffs, selectively stages files (never git add -A), and generates structured PR descriptions with test plans.
The full cycle, all through conversation:
1. Create branch ← Claude picks a name matching your convention
2. Write code ← Claude's core strength
3. Stage changes ← Claude adds specific files, skips .env and secrets
4. Commit ← Claude reads the diff and writes the message
5. Create PR ← Claude generates title, summary, test plan
6. Code review ← Claude reviews other people's PRs tooWhy "never git add -A"? Because git add -A stages everything — including .env files, credentials, large binaries, and temporary files. Claude always uses git add <specific-files> after checking what changed. This is a security practice, not just style.
GSD's principle applies here: one logical change per commit, atomic tasks. Claude naturally follows this when you give it focused instructions.
The Checkpoint System
Every time Claude edits a file, it creates a Git checkpoint. Think of it as auto-save in a video game.
Edit 1: Add dark mode CSS → checkpoint #1
Edit 2: Add toggle component → checkpoint #2
Edit 3: Wire up state management → checkpoint #3
Edit 4: Refactor (breaks things) → checkpoint #4
Oh no, Edit 4 was bad:
/rewind → pick checkpoint #3
→ Everything is back to the working state after Edit 3This is a safety net. It means you can tell Claude "try an aggressive refactor" without risk. If it doesn't work out, /rewind and you're back. The checkpoint system uses Git's staging area and stash mechanism under the hood, so your actual branch history stays clean.
Git Worktree: Actual Parallelism
Git worktree creates multiple working directories from the same repo, each on a different branch. Combined with Claude Code, this means you can run parallel sessions working on completely independent features.
my-app/ ← main branch
my-app-feature-dark/ ← worktree: feature/dark-mode
my-app-feature-api/ ← worktree: feature/api-v2
Three terminals, three Claude sessions, zero interference.This isn't branch switching. These are independent directories with independent file states. One Claude session can't accidentally overwrite another's work. Claude Code natively supports this pattern — when you launch claude in a worktree directory, it automatically detects the Git context.
Demo 8: Build Dark Mode with Proper Git Workflow
Goal
Build a dark mode toggle for a simple web app using proper Git practices: feature branch, atomic commits, and a clean merge back to main.
Steps
1. Create the Base Project
mkdir -p ~/claude-demos/demo-08 && cd ~/claude-demos/demo-08
git init
git branch -M main
mkdir -p src
cat > src/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo App</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>My App</h1>
<nav>
<a href="#home">Home</a>
<a href="#about">About</a>
</nav>
</header>
<main>
<p>Welcome to the demo app.</p>
</main>
<script src="app.js"></script>
</body>
</html>
EOF
cat > src/styles.css << 'EOF'
body {
font-family: system-ui, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
background: #ffffff;
color: #1a1a1a;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 1rem;
margin-bottom: 2rem;
}
nav a {
margin-left: 1rem;
color: #0066cc;
text-decoration: none;
}
EOF
cat > src/app.js << 'EOF'
console.log('App initialized');
EOF
cat > CLAUDE.md << 'EOF'
# Demo App
## Key Commands
- Open in browser: open src/index.html (macOS) or start src/index.html (Windows)
## Standards
- Vanilla HTML/CSS/JS, no frameworks
- CSS custom properties (variables) for theming
- Commit messages: Conventional Commits, English
- One logical change per commit
EOF
git add -A
git commit -m "feat: initial app with basic layout and styling"2. Let Claude Build Dark Mode
claudeI want to add dark mode to this app. Here's the plan:
1. Create a feature/dark-mode branch
2. Add CSS custom properties for colors (light theme as default)
3. Add a dark theme using a [data-theme="dark"] attribute on <html>
4. Add a toggle button in the header
5. Write the JS to toggle themes and persist the choice in localStorage
6. Make separate commits for each logical step
Go ahead.What you'll actually see:
> [Your prompt above]
╭─ Bash ──────────────────────────────────────────────╮
│ git checkout -b feature/dark-mode │
╰─────────────────────────────────────────────────────╯
> y
Switched to a new branch 'feature/dark-mode'
╭─ Read ──────────────────────────────────────────────╮
│ src/styles.css │
╰─────────────────────────────────────────────────────╯
> a
[Reading src/styles.css... 20 lines]
I'll start by refactoring the hardcoded colors to CSS custom properties.
╭─ Edit ──────────────────────────────────────────────╮
│ src/styles.css │
│ Replace hardcoded colors with CSS variables │
╰─────────────────────────────────────────────────────╯
> y
[Edited src/styles.css]
╭─ Bash ──────────────────────────────────────────────╮
│ git add src/styles.css │
│ git commit -m "refactor: use CSS custom properties │
│ for theming" │
╰─────────────────────────────────────────────────────╯
> y
[main 2a1b3c4] refactor: use CSS custom properties for theming
Now adding the dark theme color scheme...
╭─ Edit ──────────────────────────────────────────────╮
│ src/styles.css │
│ Add [data-theme="dark"] rules │
╰─────────────────────────────────────────────────────╯
> y
[...]
╭─ Bash ──────────────────────────────────────────────╮
│ git add src/styles.css │
│ git commit -m "feat: add dark theme color scheme" │
╰─────────────────────────────────────────────────────╯
> y
[feature/dark-mode 5d6e7f8] feat: add dark theme color scheme
Now I'll add the toggle button and JavaScript...
╭─ Edit ──────────────────────────────────────────────╮
│ src/index.html │
│ Add toggle button in header │
╰─────────────────────────────────────────────────────╯
> y
╭─ Edit ──────────────────────────────────────────────╮
│ src/app.js │
│ Add theme toggle with localStorage persistence │
╰─────────────────────────────────────────────────────╯
> y
╭─ Bash ──────────────────────────────────────────────╮
│ git add src/index.html src/app.js │
│ git commit -m "feat: add dark mode toggle with │
│ localStorage persistence" │
╰─────────────────────────────────────────────────────╯
> y
[feature/dark-mode 9a0b1c2] feat: add dark mode toggle...
Done! Three atomic commits on the feature/dark-mode branch.3. Verify the Work
git log --oneline --all --graphExpected output:
$ git log --oneline --all --graph
* 9a0b1c2 (HEAD -> feature/dark-mode) feat: add dark mode toggle with localStorage persistence
* 5d6e7f8 feat: add dark theme color scheme
* 2a1b3c4 refactor: use CSS custom properties for theming
* 1234567 (main) feat: initial app with basic layout and styling4. Merge Back
Still in the Claude session:
Switch back to main and merge feature/dark-mode. Then show me the final git log.What Just Happened?
What Claude did right:
- Created a feature branch following the convention from CLAUDE.md
- Made 3 atomic commits — each is one logical change, independently reviewable
- Generated commit messages by reading the actual diff
- Selectively staged files — always
git add <file>, nevergit add -A - Added the
Co-Authored-Byfooter to indicate AI involvement
Demo 9: Parallel Development with Worktree
Goal
Work on two features simultaneously: a frontend enhancement and a backend API, in parallel Claude sessions that don't interfere with each other.
Steps
1. Create Worktrees from Demo 8
cd ~/claude-demos/demo-08
git checkout main
# Create worktrees for two features
git worktree add ../demo-08-search feature/search
git worktree add ../demo-08-api feature/api-endpointsWhat this creates:
$ git worktree list
/home/user/claude-demos/demo-08 1234567 [main]
/home/user/claude-demos/demo-08-search 1234567 [feature/search]
/home/user/claude-demos/demo-08-api 1234567 [feature/api-endpoints]2. Terminal 1: Frontend Feature
Open a terminal:
cd ~/claude-demos/demo-08-search
claudeAdd a search feature to this app:
1. Add a search input in the header
2. Add some sample content paragraphs to the main section
3. Implement client-side search that highlights matching text and hides non-matching paragraphs
4. Commit when done3. Terminal 2: Backend Feature
Open a second terminal simultaneously:
cd ~/claude-demos/demo-08-api
claudeAdd a simple API backend:
1. Create a server.js using Node's built-in http module (no Express needed)
2. Implement GET /api/status that returns { status: "ok", uptime: process.uptime() }
3. Implement GET /api/config that returns the app configuration
4. Add a start script to package.json (create it if it doesn't exist)
5. Commit when done4. Both Sessions Run Simultaneously
While both Claude sessions are working, neither knows about the other. They're in completely separate directories with separate file states. No merge conflicts from concurrent edits.
5. Merge Both Features
cd ~/claude-demos/demo-08
git merge feature/search
git merge feature/api-endpoints
git log --oneline --all --graph6. Clean Up
git worktree remove ../demo-08-search
git worktree remove ../demo-08-apiWhat Just Happened?
Why worktrees instead of branch switching?
| Approach | Problem |
|---|---|
| Branch switching | Must stash or commit before switching. Only one session at a time. Risk of forgetting which branch you're on. |
| Git worktree | Each feature has its own directory. Multiple Claude sessions work truly in parallel. Complete isolation by design. |
This pattern becomes critical in Ch9 Agent Teams, where you'll have 3+ Claude agents working on the same repo simultaneously.
Demo 10: Break Things, Then /rewind
Goal
Intentionally let Claude make a bad refactor, then recover using the checkpoint system. This builds confidence to let Claude try bold changes without fear.
Steps
1. Set Up a Working App
mkdir -p ~/claude-demos/demo-10 && cd ~/claude-demos/demo-10
git init
cat > calculator.py << 'EOF'
"""A simple calculator module with history tracking."""
class Calculator:
def __init__(self):
self.history = []
def add(self, a, b):
result = a + b
self.history.append(f"{a} + {b} = {result}")
return result
def subtract(self, a, b):
result = a - b
self.history.append(f"{a} - {b} = {result}")
return result
def multiply(self, a, b):
result = a * b
self.history.append(f"{a} * {b} = {result}")
return result
def divide(self, a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
result = a / b
self.history.append(f"{a} / {b} = {result}")
return result
def get_history(self):
return list(self.history)
def clear_history(self):
self.history.clear()
if __name__ == "__main__":
calc = Calculator()
print(calc.add(10, 5))
print(calc.subtract(10, 3))
print(calc.multiply(4, 7))
print(calc.divide(15, 3))
print("History:", calc.get_history())
EOF
cat > test_calculator.py << 'EOF'
"""Tests for the calculator module."""
import pytest
from calculator import Calculator
def test_add():
calc = Calculator()
assert calc.add(2, 3) == 5
def test_subtract():
calc = Calculator()
assert calc.subtract(10, 4) == 6
def test_multiply():
calc = Calculator()
assert calc.multiply(3, 7) == 21
def test_divide():
calc = Calculator()
assert calc.divide(10, 2) == 5.0
def test_divide_by_zero():
calc = Calculator()
with pytest.raises(ValueError):
calc.divide(10, 0)
def test_history():
calc = Calculator()
calc.add(1, 2)
calc.subtract(5, 3)
history = calc.get_history()
assert len(history) == 2
assert "1 + 2 = 3" in history[0]
EOF
git add -A
git commit -m "feat: calculator with tests"2. Let Claude Make Progressive Changes
claudeFirst, a good change:
Add power() and sqrt() methods to the Calculator class, with history tracking and tests. Run the tests to make sure everything passes.> [prompt above]
[Claude edits calculator.py, adds power() and sqrt()]
[Claude edits test_calculator.py, adds new tests]
╭─ Bash ──────────────────────────────────────────────╮
│ python -m pytest test_calculator.py -v │
╰─────────────────────────────────────────────────────╯
> y
test_calculator.py::test_add PASSED
test_calculator.py::test_subtract PASSED
test_calculator.py::test_multiply PASSED
test_calculator.py::test_divide PASSED
test_calculator.py::test_divide_by_zero PASSED
test_calculator.py::test_history PASSED
test_calculator.py::test_power PASSED
test_calculator.py::test_sqrt PASSED
8 passed ✓Good. Tests pass. Checkpoint created.
Second, another good change:
Add a memory feature: store_memory(slot_name, value) and recall_memory(slot_name). With tests.Good. Tests still pass. Another checkpoint.
Now, the risky one:
Refactor the entire Calculator class to use a plugin architecture. Each operation (add, subtract, etc.) should be a separate plugin class that registers with the calculator. This is a major refactor -- go for it.This is a big change. Claude will rewrite most of the file. Maybe the tests break. Maybe the refactor overcomplicates things. It doesn't matter.
3. Recover with /rewind
/rewindWhat you'll see:
> /rewind
Available checkpoints:
[1] feat: calculator with tests (initial)
[2] Added power() and sqrt() methods ← 2 min ago
[3] Added memory feature ← 1 min ago
[4] Plugin architecture refactor ← just now
Rewind to which checkpoint? (1-4): 3Pick checkpoint 3 — the state where power, sqrt, and memory all work.
4. Verify the Recovery
Run pytest to confirm everything works╭─ Bash ──────────────────────────────────────────────╮
│ python -m pytest test_calculator.py -v │
╰─────────────────────────────────────────────────────╯
10 passed ✓Tests pass. The aggressive refactor is gone. Power, sqrt, and memory are intact.
What Just Happened?
The lesson: /rewind means you never need to be afraid of Claude trying something ambitious. "Try a functional programming approach." "Rewrite this in a completely different pattern." "What if we used composition instead of inheritance?" Ask freely. If it doesn't work, /rewind takes you back in seconds.
When Things Go Wrong
Merge Conflicts
$ git merge feature-branch
Auto-merging src/styles.css
CONFLICT (content): Merge conflict in src/styles.css
Automatic merge failed; fix conflicts and then commit the result.Fix: Start Claude in the conflicted directory:
claudeResolve the merge conflict in src/styles.css. Keep functionality from both sides.Claude reads the conflict markers (<<<<<<<, =======, >>>>>>>), understands the intent of both branches, and produces a clean merge. It's good at this because it can see the full context of both changes.
/rewind Goes Too Far Back
If you accidentally rewind past a change you wanted to keep, check git reflog:
git reflog
# Find the commit hash of the state you want
git checkout <hash> -- <file>Worktree Branch Already Exists
$ git worktree add ../demo-api feature/api
fatal: 'feature/api' is already checked out at '/path/to/other'Fix: Either remove the existing worktree first (git worktree remove <path>) or create a new branch name (feature/api-v2).
Claude Stages Unwanted Files
If Claude ever stages something you didn't want committed:
Unstage that file. I don't want credentials.json in the commit.Or manually: git reset HEAD credentials.json
Going Deeper
Pipe Mode for Instant Code Review
This is one of the most underrated features:
# Review uncommitted changes
git diff | claude -p "Review these changes. Flag security issues, performance concerns, and style violations."
# Review a specific branch
git diff main..feature/dark-mode | claude -p "Review this PR. Would you approve it?"
# Review the last commit
git show | claude -p "Rate this commit from 1-10 on: message quality, change cohesion, and test coverage."
# Generate a changelog from commits
git log --oneline v1.0..v2.0 | claude -p "Generate a user-facing changelog from these commits. Group by feature/fix/breaking."Example pipe mode review output:
$ git diff | claude -p "Review these changes for security issues"
## Security Review
**1 issue found:**
- **HIGH**: `src/api.js:42` — User input is passed directly to `eval()`.
This is a code injection vulnerability. Use `JSON.parse()` instead
if you're parsing JSON, or a sandboxed evaluator if you genuinely
need dynamic evaluation.
**No other security concerns.** The rest of the changes (CSS updates,
README edits) are safe.Pipe mode is non-interactive — Claude reads stdin, answers, and exits. Perfect for integrating into scripts and CI pipelines. More on this in Ch5 Hooks.
How Claude Writes Commit Messages
Claude doesn't guess. It follows a process:
- Reads
git diff --stagedto see exactly what changed - Checks
git logto match the project's existing commit style - Groups related files into single commits
- Skips
.env, credentials, and other sensitive files - Adds a
Co-Authored-By: Claude ...footer
Example of a well-crafted commit message:
feat(auth): add JWT token refresh endpoint
- Add POST /auth/refresh endpoint with rate limiting
- Persist refresh tokens in separate table with encryption
- Add audit logging for token refresh events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>PR Creation
If you have a GitHub remote and gh CLI installed:
Create a PR from the current branch to main. Include a descriptive title, a summary of changes with context, and a test plan.Claude uses gh pr create and generates a structured PR body. The description references what was changed and why, not just a list of files.
Knowledge Check
Exercise: Full Git Workflow, Conversation Only
Task
Do an entire Git workflow using only conversation with Claude. You may not type any git commands yourself.
- Initialize a new Git repo
- Create a project: a Node.js CLI tool that converts Markdown to HTML
- Commit the initial code
- Create a
feature/frontmatterbranch - Add YAML frontmatter parsing support
- Commit with a good message
- Switch back to main
- Merge the feature branch
- Show the final git log
Success Criteria
- [ ] Zero manual git commands typed
- [ ] At least 3 commits exist with Conventional Commits format
- [ ] Feature branch was created and merged properly
- [ ]
git log --graphshows the branch history - [ ] Claude used selective staging (not
git add -A)
Hint
Initialize a git repo, create a Node.js CLI tool in index.js that reads
a markdown file and converts it to HTML using a simple parser (no external deps).
Add a package.json with a bin entry. Commit everything.Create a feature/frontmatter branch. Add YAML frontmatter parsing -- when the
markdown starts with --- delimited YAML, extract it as metadata and include
it as <meta> tags in the HTML output. Commit.Switch to main, merge feature/frontmatter, show git log --graph --oneline.Chapter Summary
- Claude handles the full Git lifecycle: branch, code, stage, commit, PR, review
- Atomic commits are the standard. One logical change per commit, selective staging, meaningful messages.
- Checkpoints auto-save on every edit.
/rewindis your undo button — it lets you try bold changes fearlessly. - Git Worktree enables real parallel development. Multiple Claude sessions, multiple features, zero conflicts.
- Pipe mode (
git diff | claude -p) is instant code review. Use it before every PR. - Claude writes commit messages by reading diffs, not guessing. It matches your project's existing style.
- When merge conflicts happen, Claude can resolve them by understanding the intent of both branches.
You've finished the Beginner Stage.
Everything you've learned — tool-calling, CLAUDE.md, context management, Git workflows — comes together in the Capstone Project. It's not a todo app. You're going to build something you'll actually use.
Or skip ahead to Stage 2: Intermediate — Chapter 5: Hooks System to start automating Claude Code itself.