← Claude for Real Work

Git

Lesson 19 of 28 · 12 min · Updated

Git in Claude Code is ordinary git with somebody else at the keyboard: Claude Code runs git commands like any other command, so everything in this lesson is things you already know how to do. What changes is who types them and who reads the diff first.

The thing worth getting right is which of those two jobs is yours. Writing a commit message is a chore and it delegates well. Deciding what is in the commit does not.

Commit before you start, not after

The single habit that makes the rest of this comfortable: a clean working tree before you ask for anything ambitious.

git status

If that is not empty, git stash or commit first. With a clean tree, git diff is exactly what this session did, and git checkout -- . throws away exactly this session. Without one, you are trying to separate your changes from its changes by reading, which is the thing you were trying to avoid.

Let it write the message

This is the delegation that pays for itself daily, because a commit message is a summary of a diff and summarising a diff is the thing it is best at.

Here is the one from lesson 12, which is the commit you already made:

Commit the working tree change on this branch. Write the message yourself from the
diff: a subject line under 72 characters and a short body saying what was wrong.
Do not push.

What came back:

Fix off-by-one in paginate start index

start was computed as clamped * pageSize, skipping an extra page of
rows on every call (page 1 returned rows starting at pageSize instead
of 0). Use (clamped - 1) * pageSize.

That is a better message than most people write for a one-line fix, and it took no thought at all. It is accurate because the diff was in front of it.

Give it the shape you want. “Subject under 72 characters”, “say what was wrong rather than what you changed”, “reference the issue number”. It follows formatting instructions reliably, which is not true of everything in this course.

By default Claude Code adds a Co-Authored-By: Claude ... trailer to commits it makes, and a similar footer to pull request bodies. If your team would rather it did not, the attribution setting takes commit and pr keys, and an empty string turns each off.

Review before you commit, not after

The useful move is to make Claude read its own work before it is in history:

Show me the diff you are about to commit and tell me anything in it I did not ask for.

It is oddly good at this, because the question is a reading task and it is not defending the work. Formatting changes, a stray console.log, a file it touched while exploring: these surface here rather than in a review three days later.

The second question is worth asking as a habit:

Is anything in this diff untested?

Branches

Nothing special. It runs git checkout -b when you ask.

The reason to bother, which is stronger with Claude Code than without it: a session that goes wrong is throwaway if it is on a branch and a problem if it is on main. Lesson 16 said to /clear a session that has gone sideways. Doing that on a branch means the code goes with the conversation.

Two things worth pinning in .claude/settings.local.json, from lesson 18:

{
  "permissions": {
    "allow": ["Bash(git status)", "Bash(git diff *)", "Bash(git log *)"],
    "deny": ["Bash(git push *)"]
  }
}

Reading git is safe and constant. Pushing is a decision, and it is the one thing in a session that other people can see.

Pull requests

With gh installed and authenticated, Claude Code can open one:

Open a pull request against main. Title from the commit subject, body describing what
was wrong and how to verify it.

The body is the part worth delegating. It has the diff, the commit messages and the test output, and a PR description assembled from those beats one written from memory.

Two things to check before it goes up, both because a pull request is public inside your team:

  • The base branch. It will pick main unless you say otherwise, and on a repository with a develop convention that is wrong every time.
  • What it claims. “Adds tests for the pagination edge cases” is worth checking against the diff. This is not it lying; it is a summary written before the last change landed.

Reading history

Lesson 13 used git log to understand a file. The same trick works for a change:

What changed in src/reports/index.ts between the first commit that touched it and now,
and why did it get so long?

And the more specific one, when you are about to delete something:

git blame src/util/dates.ts

Ask which commit introduced startOfDay and whether anything ever used it. History is the cheapest way to tell load-bearing code from left-over code, and it is context nobody feeds it unless asked.

What should you not delegate?

Rebases and history rewrites. git rebase -i is not available to it, and a rewrite you did not perform yourself is a rewrite you cannot undo confidently.

Resolving conflicts on code you have not read. It will produce something that compiles. Whether it produces what both sides meant is a different question, and a merge conflict is precisely a place where the intent is not in the diff.

Pushing. Not because it is dangerous, but because it is the moment the work stops being yours alone. Make it a decision you take.

Your turn

You need a clean tree and something small to put in it. If you committed at the end of lessons 12 and 14 you have the first already; check with git status and commit anything outstanding before you start.

Then take the discovery from lesson 14: startOfDay and endOfDay in src/util/dates.ts are exported and called by nothing.

git checkout -b remove-dead-dates

Delete both functions, then in a session:

Commit the working tree change on this branch. Write the message yourself from the
diff: a subject line under 72 characters and a short body saying why this is safe to
remove. Do not push.

Then check what landed:

git show --stat --format="" HEAD
git log -1 --format=%s | wc -c
git log -1 --format=%b
npm test

Check: git show --stat lists src/util/dates.ts and nothing else, the subject line is under 72 characters, the tests still report 86 passing, and the body says why it is safe rather than what changed. “Removed two functions” is a summary of the diff; “neither was called from src/ or test/, confirmed with grep” is a summary of the reasoning. You want the second one, and it is the sentence a reviewer actually needs.

If git show --stat lists more than one file, you had uncommitted work from an earlier lesson and it has just gone into this commit with it. That is the whole argument for the first line of this lesson, learned the expensive way.

Then ask it, before you push anywhere: “Show me this diff and tell me anything in it I did not ask for.” On a one-line fix the answer should be “nothing”. Ask it again after a session that touched six files, and it will not be.

Recap

Clean tree before you start, so git diff means “what this session did”.

Delegate the commit message; it is a summary of a diff and it has the diff. Delegate the pull request body for the same reason. Keep the decisions: what goes in the commit, what the base branch is, and whether it gets pushed.

Make it review its own diff before committing. It finds the stray changes, because reading is what it is good at and it is not defending anything.

Work on branches, so that clearing a bad session throws the code away with it.

Next: the thing you type three times a week, written down once.