3.6 · Git Discipline for Agents

3.6 · 15 min

Git Discipline for Agents

An agent worked for two hours across one long session. It refactored the auth layer, added a feature, fixed a flaky test, and reformatted half the repo, and it committed all of it once at the end as "updates". Now the feature is subtly broken, and the fix is buried in a 40-file commit alongside four unrelated changes. You cannot revert it without losing the good work, you cannot bisect to find it because there is nothing to bisect, and you cannot tell which change caused the break by reading the diff, because the diff is the size of a small novel. The agent did the work. It just left you no way to undo any part of it.

History is your undo button, and agents generate fast enough to need it

Every other part of this course is about steering the agent well. This lesson is about what saves you when steering fails anyway, which it will. Git history is the recovery layer for agentic work, and it only works if the history has the right shape: small commits at gate boundaries, one concern each, so that any single change can be inspected, reverted, or bisected in isolation. An agent produces more diff per hour than a human does, so the discipline that was optional at human speed becomes the difference between a ten-second revert and an afternoon of archaeology.

The rule is the one from 3.2, extended to version control: the verification gate is the unit of work, so the gate is also the commit boundary. Work converges, the gate goes green, you commit. That is the moment the change is both proven and small, which is exactly the moment it is cheapest to record as a revertible unit.

Commit at every gate, small and conventional

Three properties make a commit history a usable recovery layer.

  • One concern per commit. A commit should be the smallest change that passes a gate on its own: the feature, or the bug fix, or the formatting sweep, never all three. When a dispatch's OUT OF SCOPE section (3.1) is doing its job, this falls out naturally, because the diff only contains the one thing the dispatch allowed. A formatting sweep is a legitimate commit; it is just its own commit, with its own message, so a later revert of the feature does not drag the reformatting back with it.
  • Conventional messages. feat(auth): add SAML SSO login, fix(digest): correct timezone in email send, test(rate-limit): lock the window boundary. The prefix is not decoration: it tells you at a glance what a commit was for when you are scanning git log --oneline during a recovery, and it is the same convention CLAUDE.md already tells the agent to follow in this very course. Ask the agent to write the message from the actual diff, then read it: a message that does not match the diff is an early warning that the diff contains more than you asked for.
  • Commit at the gate, not after five gates. The failure in the cold open was one commit for a session's worth of work. The fix is to close the loop at every gate: green, commit, next. If you are pairing with the agent across several changes in one session, that is several commits, each at its own green bar. Never let "I will commit at the end" batch a session into a single unrevertible blob.

One dispatch, one branch

A dispatch is a bounded unit of work (3.1), which makes it the natural unit of isolation too. Give each dispatch its own branch so that abandoning it costs a git checkout, not a cleanup. When you are running dispatches in parallel, or when one long-running dispatch should not block the rest of your work, Claude Code's worktrees give each session its own checkout on its own branch: claude --worktree <name> (or -w) creates a linked worktree under .claude/worktrees/<name>/ on a worktree-<name> branch, so two agents can build two features against the same repo at once without stepping on each other's working tree. This course was built that way: parallel phases ran in separate worktrees and merged at their gates, which is why the STATUS.md history shows phases landing out of numeric order.

Isolation is what makes "throw it away" a real option. If a dispatch goes bad on its own branch, you delete the branch and redispatch from clean main, and nothing you care about ever saw the bad work. On a shared branch, the same bad run is now tangled into the trunk everyone builds on.

Recovery: revert-and-redispatch over forensic patching

When an agent-built change breaks something, there are two ways to respond, and the instinct most engineers reach for first is the wrong one.

Forensic patching

Ask the agent to "fix" the broken change in place. It patches the symptom, the patch interacts with the original flawed diff, a new symptom appears, you patch again. You are now debugging a change neither of you fully understands, on top of a base you never trusted, and each patch adds more diff to the pile you cannot cleanly revert.

Revert and redispatch

git revert the bad commit (or delete the bad branch). You are back to a known-good state in seconds, provably, because the commit was small and single-concern. Then redispatch the original task with a tightened spec that names the failure you just saw. The second attempt starts from clean ground with better constraints, not from a pile of patches.

Revert-and-redispatch works because of everything upstream in this part: the change is small (3.6), the spec that produced it is a written artifact you can improve (3.1), and the gate that will re-verify it is already defined (3.2). Forensic patching throws all of that away and tries to reason a tangled diff back to health, which is 1.5's "letting an agent fix what neither of you understands" wearing a git hat.

When you do not yet know which commit broke things, git bisect finds it mechanically. Mark a known-good commit and a known-bad one, and bisect walks the history between them, halving the range each step, while you run the gate at each stop and answer good or bad. On a history of small single-concern commits, bisect lands on the exact change in a handful of steps. On the cold open's one-giant-commit history, bisect has nothing to narrow: the first bad commit is the only commit, and "it is somewhere in these 40 files" is all you learn. The recovery tool is only as good as the granularity of the history you fed it.

Lablab-3-6Make your history a recovery layer

Goal: Practice the commit-at-the-gate rhythm, then recover from a deliberately planted regression two ways: revert, and bisect.

Prereqs: the agentic-practice repo from 0.2 with several existing commits and a working verification gate (a typecheck or test command).

  1. Pick a two-part task for the repo, for example: "add a --limit N flag to the repo-stats script, and separately reformat the script with your formatter." Dispatch it with an explicit instruction: commit after each part passes the gate, conventional messages, and do not combine the two into one commit.
  2. Verify the shape: git log --oneline -3 should show two distinct commits, one feat and one style/chore, not one blended commit. If it shows one, that is the cold open; redispatch step 1 with the boundary stated harder.
  3. Plant a regression to practice on: dispatch a small change that you will pretend is buggy (or hand-edit one line so a test fails), and commit it with a normal message so it looks innocent in the log.
  4. Recover by revert: git revert <that commit>, re-run the gate, confirm green. The bad change is gone and the history records both the change and its removal honestly.
  5. Recover by bisect instead: git bisect start, git bisect bad, git bisect good <a commit from before the regression>, then at each step run the gate and type git bisect good or git bisect bad. Confirm bisect names the commit you planted. git bisect reset when done.
  6. Write one sentence in STATUS.md's "Known issues" or "Just shipped" noting what you reverted and why, so the next session's boot does not rediscover a problem you already handled.

Verify

  • git log --oneline shows single-concern commits with conventional prefixes, not one omnibus commit per session.
  • After the revert, the gate is green and git log shows both the bad commit and its revert (history is honest, not rewritten).
  • git bisect identified the exact commit you planted, in fewer steps than the number of commits in the range.
  • STATUS.md reflects the recovery.
>Troubleshooting
  • The agent committed everything as one commit despite the instruction: move the commit-boundary rule out of prose and into the dispatch's VERIFICATION section ("git log must show two commits") so it is a checked outcome, not a request, and consider a hook that blocks a commit touching more than the scoped paths.
  • git bisect reports the wrong commit: your gate is non-deterministic (a flaky test), so bisect got inconsistent good/bad answers. Fix or skip the flaky check first; bisect assumes the gate tells the truth every time.
  • You reverted but the break remained: the regression spanned more than one commit, which means those commits were not single-concern. Revert the range, and note for next time that the change should have been split.

Knowledge check

Knowledge check

Q1An agent finishes a session having built a feature, fixed an unrelated bug, and reformatted three files, all committed once as 'updates'. The feature turns out to be broken. Why is this history specifically a recovery problem, beyond being untidy?
Q2A change an agent made three commits ago introduced a bug, and you have small single-concern commits throughout. The agent offers to investigate and patch it. What does this lesson recommend, and why?
Q3You want to run two feature dispatches in parallel against the same repo without their working trees colliding. What does Claude Code give you, and what is the isolation benefit?

Sources

  • Common workflows (parallel sessions with worktrees, resuming previous conversations, feeding git log to Claude): https://code.claude.com/docs/en/common-workflows (fetched July 2026)
  • Best practices for Claude Code (committing at logical units, conventional commit guidance, letting Claude write commit messages from the diff): https://code.claude.com/docs/en/best-practices (fetched July 2026)
  • Worktree mechanics (--worktree/-w, .claude/worktrees/<name>/ layout, worktree-<name> branch naming) cross-checked against this course's own build history in STATUS.md, verified July 2026.