Topic 03

Throwing Work Away Cheaply

Git

There are three ways back, and which one applies depends on where the work is. Uncommitted changes are discarded, private commits are reset, shared commits are reverted. Each takes seconds. What stops people from using them is not git but the forty minutes already spent.

Chapter 6 decided when to reject a diff. This page is the mechanics, and the one argument that keeps engineers polishing a wrong approach long after a restart would have been cheaper.

Uncommitted: Discard

When the agent's attempt was never committed, restore the tracked files to the last commit, then remove the untracked files the agent created, after a dry run that shows what would go. The agent's whole attempt is gone, and the base commit you made before asking is untouched.

Discard the attempt: tracked files first, then a dry run, then only the agent's directory
$ git restore .
$ git clean -n
Would remove notes.md
Would remove src/cragmoor/policy.py
$ git clean -f src
Removing src/cragmoor/policy.py
$ git status --short
?? notes.md
Discard the attempt: tracked files first, then a dry run, then only the agent's directory
$ git restore .
$ git clean -n
Would remove notes.md
Would remove src/policy.ts
$ git clean -f src
Removing src/policy.ts
$ git status --short
?? notes.md
Discard the attempt: tracked files first, then a dry run, then only the agent's directory
$ git restore .
$ git clean -n
Would remove notes.md
Would remove src/main/java/org/cragmoor/Policy.java
$ git clean -f src
Removing src/main/java/org/cragmoor/Policy.java
$ git status --short
?? notes.md

The dry run is the step that earns its keep. It listed your notes file next to the agent's new file, so the real cleanup was narrowed to the directory the agent worked in, and the notes survived.

A cleanup scoped to a path is the habit worth keeping. The unscoped form removes every untracked file under the current directory, and in a working repository that includes scratch scripts, exported data and half-written notes that were never meant to be committed. The agent's files are almost always inside the source directories it was asked to work in, so name those directories and leave the rest alone.

Committed, Not Shared: Reset

When the agent's commits sit on a branch only you have, move the branch back to the last good commit. The agent's commits leave the branch, and nobody else is affected, because nobody else has them. This is the right tool for a private branch and the wrong one for anything already pushed.

The agent's commits sit on a branch only you have: move the branch back
$ git log --oneline -3
18d71bd (HEAD -> refund-window-12h-attempt) wip: agent attempt, deadline helper
8ebba91 Refund diff as the agent delivered it
2941125 (refund-window-12h) Use one refund window for refunds and the cancellation screen
$ git reset --hard refund-window-12h
HEAD is now at 2941125 Use one refund window for refunds and the cancellation screen
A reset that went too far is still in the reflog
$ git reflog -2
2941125 (HEAD -> refund-window-12h-attempt, refund-window-12h) HEAD@{0}: reset: moving to refund-window-12h
18d71bd HEAD@{1}: commit: wip: agent attempt, deadline helper

The log shows the agent's two commits on top of the refund branch's last good commit, and the reset moves the attempt branch back to that commit in one step. The commits are not destroyed: the reflog still records where the branch pointed a moment ago, which is what makes a reset on a private branch safe to try.

Shared: Revert

Once a commit is pushed or merged, history is shared. Others have built on it, or at least fetched it. A reset there rewrites what they already have, and their next pull either fails or quietly brings the commit back. A revert adds a new commit that undoes the old one, so everyone's history stays intact and the undo itself is reviewable.

A revert is a change like any other, and it gets a message that says why: "Revert the 12-hour refund window: the operations decision is postponed to next month." It goes through review, it runs the tests, and it can itself be reverted when the decision comes back. That is the property that makes shared history safe to undo: nothing disappears, and every step is explained.

Discard, Reset, Revert

Discard drops uncommitted changes. Use it when the agent's attempt was never committed.

Reset moves your private branch back to an earlier commit. Use it when the agent's commits sit on a branch only you have.

Revert adds a commit that undoes an earlier one. Use it whenever the commit has been pushed or merged.

Keeping Half

Narrowing, from Chapter 6, in git terms: stage the hunks you accept, commit them, then discard the rest. Or, when the agent committed in steps, keep the commits you accepted and reset past the ones you did not. Either way the result is a branch that holds exactly the part of the answer you read and agreed with, and nothing that arrived alongside it.

The Sunk-Cost Trap

Forty minutes of redirects on one diff, the figure this page uses, feel like an investment. The honest comparison is the next ten minutes. One option is another redirect into a context full of failed attempts, which Chapter 4 showed makes each answer worse. The other is a fresh session from the base commit with a task that carries the lesson: "compute the deadline in the gym's time zone; do not edit tests; no new types".

After forty minutes on one diff, the only question is the next ten
Redirect againthe same session
The context holds every failed attempt and every correction. Each new answer is built on all of them, and a third redirect on the same point rarely lands.
Restart from the basea fresh session
A clean context and a task that states what the failures taught. Usually right in one or two rounds, because the lesson is now a constraint, not a correction.

The lesson lives in the task, not in the code. Nothing in the forty minutes of diffs is worth keeping except what they revealed about the problem, and that fits in one sentence of the new task. Restarting throws away the code of a wrong approach and keeps everything learned from it.

The trap has a recognisable feel. The redirects get longer, each one adds a constraint the previous answer broke, and the diffs start to undo each other. When a third message repeats a point the first one already made, the session has stopped converging. Stop there, write the constraints down, discard, and start again. Chapter 10 applies the same rule to a debugging session that goes in circles.

The Net Under the Net

A reset that went too far can be undone. The reflog records where each branch and HEAD pointed, move by move, so the commits a reset left behind can be found and restored. Committed work is very hard to lose in git.

Uncommitted work is different. The reflog records the movements of references, not the contents of your working tree, so it cannot bring back changes discarded before they were committed, and edits that were never staged are gone for good. That is one more reason for the commit before every request: it turns everything into the kind of work git can bring back. The reflog itself is covered in Git, GitHub & GitHub Actions, Topic 18.

Misconceptions
  • "Reset is dangerous, so avoid it." On your own unpushed branch it is the right tool, and recoverable from the reflog. On a shared branch it is the wrong one.
  • "Revert deletes the commit." It adds a new commit that undoes the change, and the original stays in history, which is what makes revert safe on shared branches.
  • "I've put too much into this attempt to start over." What is spent is spent either way. Only the cost from here counts.
  • "Restarting throws away what we learned." What was learned goes into the new task. The code of a wrong approach is not where the learning lives.
  • "Discarding removes the agent's new files." Restoring tracked files leaves untracked ones, and removing them is a separate step.
Why It Matters
  • Choose the way back by where the work is: uncommitted, private or shared.
  • Restart from the base commit with a sharper task after two failed redirects on one point. A fresh context is cheaper than a polluted one.
  • Write the lesson into the new task before discarding. The constraint the failed attempt revealed is the part worth keeping.
  • Never reset or force-push a shared branch. Revert instead.

Knowledge Check

The agent's attempt was committed as two commits on your feature branch, and the branch was pushed and merged yesterday. How do you take it back?

  • Restore the working tree to the last commit and remove new files
  • Reset the main branch back to before the two commits and push it
  • Revert the two commits, adding new commits that undo their changes
  • Use the reflog to delete the two commits from everyone's history

You have spent forty minutes redirecting one diff. What is the honest comparison to make now?

  • Whether forty minutes is too much to throw away for one change
  • Which costs less from here: another redirect, or a fresh restart
  • Whether the agent is confident that the next attempt will work
  • How many lines of code the forty minutes of attempts produced

You restart from the base commit in a fresh session. What should the new task carry over from the failed attempt?

  • The code from the attempt's best diff, pasted in as a starting point
  • The full transcript of the old session, so the agent has every detail
  • A list of each wrong line it wrote, so it can avoid them one by one
  • The constraints the failures revealed, written into the new task

Which of these can the reflog bring back?

  • Commits on your branch that a hard reset moved the branch away from
  • Edits you discarded with a restore before they were ever committed
  • Untracked notes you deleted with the cleanup command after a dry run
  • Rows that a migration the agent ran deleted from the dev database

You got correct