Module 7: Integrations: Git, SDK, and Remote Control

Git workflows with Claude Code

Git workflows with Claude Code

Overview

Git and Claude Code are a natural pair. Both live in the terminal, both work with code files, and both follow structured workflows. The difference is that now you don't have to type every git add, git commit -m "...", and git push by hand — Claude Code can run the whole Git flow for you, from creating branches to opening Pull Requests.

But "Claude Code knows Git" does not mean "let it do whatever it wants with your history". This capsule covers how to integrate Git with Claude Code under control: which operations to delegate, which patterns to follow, how to guarantee semantic commits, and how to keep a clean history when an AI agent is contributing code.

The skill isn't getting Claude to run Git commands — it's designing a workflow where Git and Claude Code make each other stronger.


Claude Code and Git: what it can do

Claude Code has full access to Git through the terminal. It doesn't use a special API — it runs the same commands you would run. That means it can do everything Git allows:

Basic operations

OperationCommandClaude Code can...
Check statusgit statusVerify which files changed
See differencesgit diffAnalyze changes line by line
Stage filesgit addStage selectively or completely
Commitgit commit -m "..."Commits with descriptive messages
See historygit logAnalyze previous commits
Create a branchgit checkout -bBranches with descriptive names
Switch branchgit checkout / git switchNavigate between branches
Mergegit mergeIntegrate branches
Pushgit pushSend changes to the remote

Advanced operations

OperationWhat Claude Code does
Pull RequestsUses gh pr create to open PRs with a detailed description
Code reviewAnalyzes diffs and explains what changed and why
Merge conflictsReads the conflicts, understands both sides, proposes a resolution
Interactive rebaseHelps reorganize, squash, or edit commits
Amend commitsModifies the last commit (message or content)
Cherry-pickPicks specific commits from other branches
StashSaves changes temporarily
BisectFinds the commit that introduced a bug

What Claude Code brings that Git doesn't have

The key difference: Claude Code understands the context of the changes. Git knows you changed line 42 of auth.py. Claude Code knows you changed token validation because the old version didn't handle expired tokens.

Git alone:
  git commit -m "update auth.py"    ← what changed? why?

Claude Code + Git:
  git commit -m "fix(auth): handle expired JWT tokens
  
  Previously, expired tokens returned a generic 401 error.
  Now returns a specific 'token_expired' error code with
  the expiration timestamp, allowing clients to refresh
  automatically."

Workflow Pattern 1: Feature Branch

The most common pattern. Claude Code creates a branch, implements the feature, makes progressive commits, and opens a PR.

Basic example

You: Create a /api/health endpoint that returns the server's
     status. Work on a separate branch and make a commit
     when you're done.

Claude Code runs:

git checkout -b feature/health-endpoint

# ... implements the endpoint ...

git add src/routes/health.py
git commit -m "feat(api): add /health endpoint

Returns server status including uptime, version, and
database connection status. Responds with 200 for healthy
and 503 for degraded state."

Intermediate example

You: Implement the email notification system. Create a branch,
     make separate commits for each logical step, and open a PR
     when you're done.

Claude Code runs:

git checkout -b feature/email-notifications

# Step 1: model and schema
git add src/models/notification.py src/schemas/notification.py
git commit -m "feat(notifications): add Notification model and schema

SQLAlchemy model with fields: id, user_id, type, subject,
body, sent_at, read_at. Pydantic schema for validation."

# Step 2: sending service
git add src/services/email_service.py
git commit -m "feat(notifications): add email sending service

Uses SMTP with async support. Includes retry logic (3 attempts)
and template rendering with Jinja2."

# Step 3: API endpoint
git add src/routes/notifications.py
git commit -m "feat(api): add notification endpoints

POST /notifications/send - send notification
GET /notifications - list user notifications
PATCH /notifications/:id/read - mark as read"

# Step 4: tests
git add tests/test_notifications.py
git commit -m "test(notifications): add unit and integration tests

Covers email sending, retry logic, template rendering,
and all API endpoints. Uses mock SMTP for unit tests."

# Open the PR
gh pr create \
  --title "feat: email notification system" \
  --body "## Summary
- New Notification model with SQLAlchemy
- Email service with async SMTP and retry logic  
- REST endpoints for sending and managing notifications
- Full test coverage

## Testing
- Run: pytest tests/test_notifications.py
- Manual: POST to /notifications/send with valid payload"

When to use this pattern

  • New features that touch multiple files
  • Changes that need code review before merge
  • Work that may take several Claude Code sessions
  • Projects with CI/CD that runs tests on PRs

Workflow Pattern 2: Commit-as-you-go

Claude Code makes a commit after every meaningful change. The history reflects the progress step by step.

Basic example

You: Refactor the authentication module. After each logical
     change, make a commit explaining what you did and why.

Claude Code works incrementally:

git commit -m "refactor(auth): extract token validation to separate module

Moves JWT validation logic from auth_middleware.py to
token_validator.py. Reduces middleware complexity and
makes validation reusable across routes."

# ... next change ...

git commit -m "refactor(auth): replace hardcoded expiry with config

Token expiration was hardcoded to 3600s. Now reads from
AUTH_TOKEN_EXPIRY env var with 3600s default."

# ... next change ...

git commit -m "refactor(auth): add refresh token rotation

Implements refresh token rotation: each refresh generates
a new refresh token and invalidates the previous one.
Prevents refresh token replay attacks."

When to use this pattern

  • Refactors where you want a granular history
  • Long sessions where every step must be reversible
  • When you work without a PR (commits straight to a development branch)
  • To learn: each commit documents what Claude did and why

Workflow Pattern 3: Review Before Commit

Claude Code shows the changes, you review them, and then Claude commits. The safest pattern for critical code.

Basic example

You: Fix the bug in the price calculation. Show me the diff
     before committing — I want to review it.

Claude Code:

I've identified the bug and applied the fix. Here's the diff:

--- a/src/services/pricing.py
+++ b/src/services/pricing.py
@@ -45,7 +45,7 @@ def calculate_total(items, discount):
-    total = sum(item.price for item in items)
-    return total - discount
+    subtotal = sum(item.price * item.quantity for item in items)
+    discount_amount = subtotal * (discount / 100)
+    return max(subtotal - discount_amount, 0)

The bug was twofold:
1. It didn't multiply price × quantity
2. It applied the discount as an absolute value instead of a percentage

Want me to make the commit?
You: Yes, go ahead.
git add src/services/pricing.py
git commit -m "fix(pricing): correct total calculation with quantity and percentage discount

Two bugs fixed:
1. Price calculation now multiplies by item quantity
2. Discount applied as percentage instead of absolute value
3. Added floor of 0 to prevent negative totals"

When to use this pattern

  • Production code or critical code
  • When you're not 100% sure what Claude is going to change
  • To build trust: you review first, you trust more later
  • Areas of the codebase you don't fully know

Conventional Commits with Claude Code

Conventional commits are a standard for commit messages that makes it easy to generate changelogs, do semantic versioning, and understand the history.

The format

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

The types Claude Code uses

TypeWhen it's usedExample
featNew featurefeat(api): add user registration endpoint
fixBug fixfix(auth): handle expired tokens correctly
docsDocumentation onlydocs(readme): add installation instructions
styleFormatting, no logic changestyle(lint): fix indentation in models
refactorCode change that's neither a fix nor a featurerefactor(db): extract query builder
testAdding or fixing teststest(auth): add token expiration tests
choreMaintenance (deps, configs)chore(deps): update fastapi to 0.104
perfPerformance improvementperf(queries): add index on user_email
ciCI/CD changesci(github): add Python 3.12 to test matrix

How to ask Claude Code to use conventional commits

In your CLAUDE.md:

# Git conventions

- Use conventional commits: type(scope): description
- Types: feat, fix, docs, style, refactor, test, chore, perf, ci
- Scope: the module or area affected (auth, api, db, ui, etc.)
- Description: imperative, lowercase, no trailing period
- Body: explain the "why", not the "what" (the diff already shows the what)
- Max 72 characters on the first line

With that instruction in CLAUDE.md, Claude Code will produce consistent commits automatically.


Pull Requests with Claude Code

Claude Code can create complete Pull Requests using the gh CLI (GitHub CLI). It doesn't just run the command — it generates detailed descriptions based on the actual changes.

Basic example

You: Open a PR for this branch.
gh pr create \
  --title "feat: add email notification system" \
  --body "## Summary
Adds a complete email notification system with async SMTP support.

## Changes
- New \`Notification\` model (SQLAlchemy)
- Email service with retry logic and Jinja2 templates
- REST endpoints: send, list, mark as read
- Full test coverage (unit + integration)

## Testing
\`\`\`bash
pytest tests/test_notifications.py -v
\`\`\`

## Screenshots
N/A (backend only)"

Advanced example: a PR with full context

You: Open a PR with a detailed description. Include what changed,
     why, how to test it, and any breaking changes.

Claude Code analyzes every commit on the branch, the diffs, and generates:

gh pr create \
  --title "refactor: migrate auth from sessions to JWT" \
  --body "## Summary
Migrates the authentication system from server-side sessions to 
stateless JWT tokens. This eliminates the Redis session store 
dependency and enables horizontal scaling.

## Motivation
The current session-based auth requires a shared Redis instance 
across all server replicas. With JWT, each request is self-contained 
and any replica can validate it independently.

## Changes
- \`src/auth/\`: replaced session middleware with JWT middleware
- \`src/models/user.py\`: added \`refresh_token_hash\` field
- \`src/routes/auth.py\`: new login/refresh/logout endpoints
- \`config/\`: added JWT_SECRET, TOKEN_EXPIRY settings
- Removed: \`src/sessions/\`, Redis configuration

## Breaking Changes
⚠️ **All existing sessions will be invalidated.** Users need to 
log in again after deployment.

API changes:
- \`POST /auth/login\` now returns \`{ access_token, refresh_token }\`
- All authenticated endpoints require \`Authorization: Bearer <token>\`
- \`POST /auth/refresh\` replaces automatic session refresh

## Migration Guide
1. Deploy the new code
2. Run \`python manage.py migrate\` for the new DB field
3. Remove Redis session store configuration
4. Update frontend to use token-based auth

## Testing
\`\`\`bash
pytest tests/test_auth.py -v
pytest tests/test_integration.py -v
\`\`\`

## Checklist
- [x] Unit tests pass
- [x] Integration tests pass
- [x] Migration script tested
- [x] Documentation updated"

Code review with Claude Code

Claude Code can act as a code reviewer. It can analyze diffs, spot problems, and explain changes.

Reviewing a diff

You: Review the changes I made today. Anything to improve?

Claude Code runs git diff (or git diff --staged), analyzes the changes, and answers:

I reviewed your changes. Observations:

1. **src/services/user.py:34** - The query has no limit. If the table 
   has millions of rows, this is going to be a problem:
   
   users = db.query(User).filter(User.active == True).all()
   
   I'd suggest adding pagination or at least a .limit().

2. **src/routes/users.py:67** - The endpoint accepts a user_id as a 
   string but doesn't validate it. An invalid UUID will cause a 500 
   instead of a 400.

3. **tests/test_users.py** - Good coverage, but there's no test for the 
   user-not-found case (404).

Want me to apply these fixes?

Reviewing someone else's PR

You: Review PR #42 on GitHub. Focus on security and performance.
gh pr diff 42

Claude Code reads the PR diff and gives feedback focused on what you asked for.


Resolving merge conflicts

When a merge produces conflicts, Claude Code can understand both sides and propose an informed resolution.

Example

You: I have merge conflicts. Resolve them, keeping the
     functionality from both branches.

Claude Code reads the conflicted files:

<<<<<<< HEAD
def calculate_price(item, quantity):
    base = item.price * quantity
    return base * 0.9  # 10% member discount
=======
def calculate_price(item, quantity, discount_code=None):
    base = item.price * quantity
    if discount_code:
        discount = get_discount(discount_code)
        return base * (1 - discount)
    return base
>>>>>>> feature/discount-codes

And proposes:

def calculate_price(item, quantity, discount_code=None):
    base = item.price * quantity
    if discount_code:
        discount = get_discount(discount_code)
        return base * (1 - discount)
    return base * 0.9  # 10% member discount (default when no code)

Claude Code understands that HEAD added a fixed member discount and the other branch added support for discount codes. The resolution keeps both behaviors.


Comparisons and decisions

When should Claude Code make the commits vs when should you?

SituationRecommendation
New feature on a separate branch✅ Claude Code makes the commits
Production fix⚠️ Review before commit
Large refactor✅ Claude Code with commit-as-you-go
Changes to sensitive configuration❌ Do them yourself, by hand
Merge into main/master⚠️ You do the merge, Claude Code prepares the PR

Branch or direct commit?

SituationRecommendation
Any new featureAlways a branch
Small fix (typo, formatting)A direct commit is acceptable
RefactorBranch + PR
DocumentationDepends on the team
Experimenting with Claude CodeAlways a branch (easy to throw away)

Common patterns

Pattern: Git status at the start of a session

Add this to your CLAUDE.md:

# At the start of a session
- Run git status to check the state of the repo
- If there are uncommitted changes, ask me what to do before continuing
- If we're on a feature branch, continue the work

Pattern: Atomic commits

You: Implement these changes. Make atomic commits — each commit
     should do exactly one thing, and the project must build
     after every commit.

Claude Code makes sure that:

  1. Every commit is independent and builds
  2. The tests pass after every commit
  3. The message describes exactly what changed

Pattern: Branch protection

In your CLAUDE.md:

# Git rules
- NEVER push directly to main
- NEVER force push
- Always work on feature branches
- Name branches: feature/*, fix/*, docs/*, refactor/*

Git safety protocols in Claude Code

Claude Code has built-in safety rules to protect your repository. These rules are automatic — you don't have to configure them.

Mandatory rules

1. Co-Authored-By on every commit:

Every commit created by Claude Code automatically includes:

Co-Authored-By: Claude <noreply@anthropic.com>

This makes it possible to trace which code was AI-generated — important for audits, compliance, and transparency.

2. Never force push:

Claude Code will never run git push --force unless you explicitly ask for it. And even if you ask, it will warn you if the target is main or master.

3. Never skip hooks:

The --no-verify and --no-gpg-sign flags are never used automatically. If a pre-commit hook fails, Claude Code investigates and fixes the problem instead of skipping the validation.

4. New commits over amend:

Claude Code always prefers creating a new commit over using --amend. Why? If a pre-commit hook failed, the commit was NOT created — running --amend would modify the PREVIOUS commit, potentially destroying earlier work.

5. Selective staging:

Claude Code stages specific files by name (git add file.py) instead of git add -A or git add ., to avoid accidentally including sensitive files (.env, credentials) or large binaries.

What this means for you

✅ You can trust that Claude Code won't destroy your history
✅ Pre-commit hooks always run
✅ Commits carry AI traceability
✅ Sensitive files don't get included by accident

Pitfalls and edge cases

Pitfall 1: Claude Code pushes without you asking

By default, Claude Code asks permission before running git push. But if you configured Execute(git push*) in allowedTools, it will do it without asking. Make sure your permissions are the ones you want.

Fix: Don't put git push in allowedTools if you want to review before pushing.

Pitfall 2: Giant commits

Sometimes Claude Code piles up a lot of changes and makes one enormous commit. That makes code review and rollback painful.

Fix: Explicitly ask for granular commits:

Make separate commits for each logical change. 
Don't pile multiple changes into a single commit.

Pitfall 3: Generic commit messages

If you don't tell Claude Code what commit style to use, it may produce messages like "update files" or "fix bug".

Fix: Conventional commits in CLAUDE.md (see the section above).

Pitfall 4: Conflicts with local changes

If you have uncommitted changes and you ask Claude Code to check out another branch, it can fail or lose your work.

Fix: Claude Code should run git stash before switching branches. Add this to CLAUDE.md:

# Before switching branches
- If there are uncommitted changes, run git stash
- After returning to the original branch, run git stash pop

Pitfall 5: Accidental force push

Claude Code should never run git push --force on shared branches. But if you ask it to, it will.

Fix: Block force push in your permissions or in CLAUDE.md:

# FORBIDDEN
- git push --force (on any branch)
- git push --force-with-lease (only allowed on personal branches)
- git reset --hard on shared branches

Complete worked example

A full workflow from start to finish:

Claude Code session
───────────────────

You: I need to add a rate limiting system to the API.
     Work on a branch, make incremental commits with
     conventional commits, and open a PR when you're done.

Claude Code:
─────────────
# 1. Check the state of the repo
$ git status
On branch main
nothing to commit, working tree clean

# 2. Create the branch
$ git checkout -b feature/rate-limiting

# 3. Implement the middleware
# ... writes src/middleware/rate_limiter.py ...
$ git add src/middleware/rate_limiter.py
$ git commit -m "feat(middleware): add token bucket rate limiter

Implements token bucket algorithm with configurable rate
and burst size. Supports per-IP and per-user limiting.
Uses Redis for distributed rate limiting across replicas."

# 4. Configuration
# ... writes src/config/rate_limits.py ...
$ git add src/config/rate_limits.py
$ git commit -m "feat(config): add rate limiting configuration

Default limits: 100 req/min for authenticated users,
20 req/min for anonymous. Configurable per-endpoint
via RATE_LIMIT_* environment variables."

# 5. Wire it into the routes
# ... modifies src/main.py ...
$ git add src/main.py
$ git commit -m "feat(api): apply rate limiting middleware

Rate limiter applied globally with per-endpoint overrides.
Auth endpoints: 10 req/min (brute force protection).
Upload endpoints: 5 req/min (resource protection)."

# 6. Tests
# ... writes tests/test_rate_limiter.py ...
$ git add tests/test_rate_limiter.py
$ git commit -m "test(rate-limiting): add comprehensive test suite

Tests cover: token bucket refill, burst handling, Redis
failure fallback, per-user vs per-IP limits, and
concurrent request handling."

# 7. Push and PR
$ git push -u origin feature/rate-limiting
$ gh pr create \
    --title "feat: add API rate limiting with token bucket" \
    --body "## Summary
    Adds rate limiting to all API endpoints using token bucket 
    algorithm with Redis backend.
    
    ## Changes
    - Token bucket rate limiter middleware
    - Configurable limits per endpoint and user type
    - Redis backend for distributed limiting
    - Comprehensive test suite
    
    ## Testing
    pytest tests/test_rate_limiter.py -v
    
    ## Configuration
    Set RATE_LIMIT_DEFAULT=100 for custom limits.
    See src/config/rate_limits.py for all options."

Practice exercises

Exercise 1: Basic — Feature branch with commits

Ask Claude Code to implement a simple piece of functionality (for example, an endpoint that returns the current date) using the feature branch pattern.

Requirements:

  • Create the branch feature/current-date
  • Implement the endpoint
  • Commit with a conventional commit
  • Verify with git log --oneline
Solution
You: Create a branch feature/current-date. Implement a GET /api/date
     endpoint that returns the current date and time in ISO format.
     Commit with a conventional commit.

Claude Code runs:

git checkout -b feature/current-date

It creates the file and then:

git add src/routes/date.py
git commit -m "feat(api): add /date endpoint returning current ISO datetime"
git log --oneline -3

You check the result with git log --oneline.

Exercise 2: Intermediate — Conventional commits in CLAUDE.md

Configure conventional commits in your CLAUDE.md and verify that Claude Code follows them.

Requirements:

  • Add a "Git conventions" section to CLAUDE.md
  • Ask Claude Code for 3 different changes (feat, fix, docs)
  • Verify that the commits follow the format
Solution

Add this to CLAUDE.md:

# Git conventions
- Conventional commits are mandatory: type(scope): description
- Allowed types: feat, fix, docs, style, refactor, test, chore
- Scope: the module affected
- Description: imperative, in English, no trailing period, max 72 chars
- Body: explain the "why" when it isn't obvious

Then ask for three changes:

1. You: Add an /api/version endpoint that returns the app version
2. You: The /api/health endpoint returns 500 when the DB is disconnected. It should return 503.
3. You: Add a README explaining how to run the project

Verify:

git log --oneline -3
# feat(api): add /version endpoint returning app version
# fix(api): return 503 instead of 500 when DB is disconnected  
# docs(readme): add project setup and run instructions

Exercise 3: Intermediate — A PR with a full description

After implementing a feature on a branch, ask Claude Code to open a PR with a detailed description.

Requirements:

  • Have at least 2 commits on a feature branch
  • A PR with: summary, changes, testing instructions, checklist
  • Use gh pr create
Solution
You: Open a PR for this branch. Include a summary of all the
     changes, testing instructions, and a review checklist.

Claude Code analyzes the commits and diffs:

gh pr create \
  --title "feat: add user notification preferences" \
  --body "## Summary
Adds the ability for users to configure their notification 
preferences (email, push, SMS) per event type.

## Changes
- \`NotificationPreference\` model with per-event settings
- CRUD endpoints for preferences
- Integration with existing notification service
- Migration script for new DB table

## Testing
\`\`\`bash
pytest tests/test_preferences.py -v
\`\`\`
Manual: POST to /api/users/me/preferences with payload

## Checklist
- [x] Unit tests
- [x] Integration tests  
- [x] Migration tested
- [ ] Frontend integration (separate PR)"

Exercise 4: Advanced — Resolve merge conflicts

Simulate a merge conflict and ask Claude Code to resolve it.

Steps:

  1. Create two branches from main
  2. On each branch, modify the same file in a different way
  3. Merge one branch into main
  4. Try to merge the second — there will be a conflict
  5. Ask Claude Code to resolve the conflict
Solution

Setup:

# Branch A: adds email validation
git checkout -b feature/email-validation
# edit src/validators.py → add validate_email()
git add . && git commit -m "feat: add email validation"

# Branch B: adds phone validation
git checkout main
git checkout -b feature/phone-validation  
# edit src/validators.py → add validate_phone()
git add . && git commit -m "feat: add phone validation"

# Merge A into main
git checkout main
git merge feature/email-validation

# Merge B → conflict
git merge feature/phone-validation
# CONFLICT in src/validators.py

Ask Claude Code:

You: I have a merge conflict in src/validators.py. Resolve it,
     keeping the functionality from both branches (email and phone
     validation).

Claude Code reads the conflict, understands that both branches added different functions to the same file, and resolves it by keeping both functions.

Exercise 5: Advanced — A complete Git workflow

Simulate a full workflow: analyze the project, plan the changes, implement on a branch, incremental commits, tests, and a PR.

Requirements:

  • Use Explore → Plan → Code
  • A feature branch with at least 3 commits
  • Passing tests
  • A PR with a detailed description
  • All of it handled by Claude Code
Solution guide
You: I want to add a caching system for the most-used endpoints.
     First analyze which endpoints would benefit most, then design
     the solution, and finally implement it on a branch with
     incremental commits. Open a PR at the end.

Claude Code should:

  1. Explore: analyze the routes, identify the most expensive ones
  2. Plan: design the caching strategy (in-memory vs Redis, TTL, invalidation)
  3. Code:
    • git checkout -b feature/api-caching
    • Commit 1: feat(cache): add caching middleware with TTL support
    • Commit 2: feat(api): apply caching to /users and /products endpoints
    • Commit 3: test(cache): add cache hit/miss and invalidation tests
  4. PR: gh pr create with a complete description

Check with git log --oneline feature/api-caching that the commits are clear and incremental.


Summary

What you learned in this capsule:

  • Claude Code runs Git commands natively from the terminal — it needs no special APIs
  • There are 3 workflow patterns: Feature branch (the most common), Commit-as-you-go (granular history), and Review before commit (maximum control)
  • Conventional commits (feat:, fix:, docs:) are configured in CLAUDE.md for automatic consistency
  • Claude Code can create PRs with gh, generating detailed descriptions based on the real diffs
  • Claude Code can do code review by analyzing diffs and explaining changes
  • For merge conflicts, Claude Code understands the context of both sides and proposes informed resolutions
  • The main pitfalls: pushing without review, giant commits, generic messages, and accidental force push
  • Configure clear rules in CLAUDE.md and permissions in settings.json for a safe workflow

Next capsule: 03 - Python and TypeScript SDK — how to access Claude Code programmatically for automation, CI/CD, and custom scripts.


Additional resources

Official documentation

Git and GitHub

Complementary

  • Hooks — Hooks to validate Git operations
  • Headless Mode — Git automation with headless mode