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 scanninggit log --onelineduring 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.
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.
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).
- Pick a two-part task for the repo, for example: "add a
--limit Nflag 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. - Verify the shape:
git log --oneline -3should show two distinct commits, onefeatand onestyle/chore, not one blended commit. If it shows one, that is the cold open; redispatch step 1 with the boundary stated harder. - 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.
- 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. - 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 typegit bisect goodorgit bisect bad. Confirm bisect names the commit you planted.git bisect resetwhen done. - 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 --onelineshows single-concern commits with conventional prefixes, not one omnibus commit per session.- After the revert, the gate is green and
git logshows both the bad commit and its revert (history is honest, not rewritten). git bisectidentified 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 bisectreports 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
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.