LEARN · PHASE 02 · LESSON 17

Stashing, and Why Agents Break Things

You can park half-finished work without committing it, and you can recognise

Type
Learn
Prerequisites
Phase 02, Lessons 12, 16; Phase 01, Lesson 10
Time
~40 minutes

Learning Objectives

  • Park half-finished work, switch branches, and get it back intact
  • Choose pop or apply deliberately, and know when -u is required
  • Recognise the three shapes of agent damage: delete, thrash, scope creep
  • Recover each shape with the smallest command that works

The Problem

You are halfway through a change. It is not committable — it is half-wired and the build does not pass. You need to switch branches to chase something else.

git switch main refuses: your working tree has changes that would be lost. Your options look like three bad ones. Commit the broken state (pollutes history), finish the change (you do not know how), or abandon it.

So you do nothing, and you lose twenty minutes.

That is the stash-shaped problem. Stash solves it in one command.

The second half of this lesson is stranger. An agent working in your folder can delete a file it has never read, overwrite a config it misunderstood, or rewrite the same file eleven times in one turn. None of these are exotic — they are what a loop does when the tool it holds is "write to this path". Once you know the three shapes, they stop being surprises.


The Concept

Stash: park work without committing it

git stash takes your uncommitted changes out of the working tree and stores them as a commit on a hidden branch. The folder returns to the last commit. Your work is still there, one command away.

git stash                 # park everything uncommitted
git switch main           # now it works
# ... do the other thing ...
git switch back
git stash pop             # put it back and drop the stash entry

pop restores and deletes the entry. apply restores and keeps the entry — the right choice when the restoration conflicted, because you get another chance without having lost the copy.

CommandRestoresDeletes the stash entry
git stash popYesYes
git stash applyYesNo — keep it until you have checked
git stash list—Shows every entry
git stash show -p stash@{0}Diffs the entryNo
git stash branch fix-xYes, as a new branchYes, and commits the work there

Two limits worth knowing before you need them under pressure:

Stash does not carry untracked files by default. A new file that was never committed is not stashed by a plain git stash, and comes back as a conflict or not at all. git stash -u includes untracked files. This is the most common stash surprise, and it is the one that costs you an agent's new files.

A stash entry is not a backup you can forget about. It is a commit on a branch with no name, and it is subject to garbage collection like anything else. Treat an unrecovered stash as work you have not actually saved.

Why agents break things

Phase 01, Lesson 10 established the agentic loop: the model decides, a tool executes, the result returns. The tool here is the filesystem, and it does not care what the file meant.

Three shapes, and they are all mechanical:

flowchart TD
    A[Agent turn] --> S{What happened?}
    S -->|Deleted a file it did not read| S1[Shape 1: destructive delete]
    S -->|Rewrote a file repeatedly| S2[Shape 2: thrash]
    S -->|Changed something outside the ask| S3[Shape 3: scope creep]
    S1 --> R[Recovery: one command, if it was committed]
    S2 --> R2[Recovery: read the diff, not the file]
    S3 --> R3[Recovery: selective restore, one file]
Mermaid source — renders as a diagram where mermaid.js is available

Shape 1 — destructive delete. An agent deletes a file it never read, reasoning that it is unused. git status shows it immediately. git checkout -- <file> puts it back, if it was committed.

Shape 2 — thrash. The agent rewrites the same file across several edits inside one turn. The final state might be right, might be worse than the middle. The only way to see the path is git diff — the final file tells you nothing about whether it improved.

Shape 3 — scope creep. It changed a config, a lock file, and a "small unrelated fix" alongside the ask. git status names every file. That is how you notice, which is why git status before every agent run is not superstition.

The protocol that follows from this

The three shapes share one weakness: they are all cheap to undo if the last known-good state was committed, and expensive if it was not.

git status              # is it clean?
git add -A && git commit -m "before: <what the agent is about to do"
# hand over the folder
git status              # what did it actually touch
git diff                # what did it actually change

Committing before handing over a folder is the single habit that makes agents safe to use. It costs one command and it converts every failure above from a loss into a review.

The reframe: stashing is not about hiding work, it is about giving work a

place to live that isn't the working tree — and an agent is only reversible in

proportion to whether you committed before letting it loose.


Build It

1. Park half-finished work

cd ~/git-drill
git switch -c half-done
printf 'work in progress\n' > wip.txt
git add wip.txt
git switch main        # this succeeds, because nothing is committed
git status             # clean

Note: wip.txt is committed here so the stash is simple. Step 4 shows the untracked case.

✅ You pass when the switch succeeded and git status is clean on main.

2. Come back and get it

git switch half-done
git status

✅ You pass when git status shows wip.txt staged, exactly as you left it.

3. Stash something genuinely uncommitted

printf 'experiment\n' >> wip.txt        # unstaged edit
printf 'brand new\n' > untracked.txt    # never added
git stash                              # note: does not take untracked.txt
git status                             # untracked.txt still there
git stash pop
git status

✅ You pass when untracked.txt survived the stash untouched, and wip.txt's edit came back — and you can now say which files a plain git stash misses.

4. Stash everything, including new files

git stash -u
git status             # genuinely clean now
git stash pop

✅ You pass when untracked.txt is inside the stash and comes back with the rest.

5. Apply instead of pop

git stash
git stash apply
git stash list         # the entry is still there
git stash drop

✅ You pass when git stash list showed the entry after apply, and git stash drop removed it.

6. Recover from an agent-shaped failure

Simulate all three shapes in a scratch repo, committing before you start.

git add -A && git commit -m "before agent"
rm important.txt                      # shape 1
printf 'something else\n' >> config   # shape 2
printf 'unrelated\n' > random.txt     # shape 3
git status
git diff
git checkout -- important.txt         # one file, not the whole tree
git status

✅ You pass when important.txt is back and git status still shows the other two changes — a selective restore, not a nuke.


Use It

Command / conceptWhen you reach for it
git stashParking uncommitted changes to switch branches
git stash -uSame, but includes files that were never added
git stash popRestoring, and clearing the entry
git stash applyRestoring, keeping the entry — use when it conflicted
git stash list"Where was that?" — entries are commits too
git stash branch <name>Turning a stash into a real branch so you can commit on it
git checkout -- <file>One file back, everything else left alone
Commit before handoverBefore any agent run. Makes every failure reversible.
git status afterWhat it actually touched
git diff afterWhat it actually changed, including thrash

Exercises

  1. Start a real change, get halfway, stash with -u, switch to main, do something unrelated, and come back. Time it. ✅ You pass when the work came back intact and you have a time in seconds — if it took under thirty seconds, the "I can't switch branches" feeling is gone for good.
  1. Simulate all three agent shapes in a scratch repo. Commit before you start. Recover each one with the smallest command that works, not reset --hard. ✅ You pass when you used checkout -- <file> or stash for at least two of them, and you can say why the third needed something different.
  1. Write the four-line protocol from the Concept section into a place you will actually run it — a shell alias, a script, a note in CLAUDE.md. ✅ You pass when the protocol exists somewhere outside this lesson, and you have used it once on a real agent run.

Key Terms

TermWhat people sayWhat it actually means
stash"hiding my work"A commit on a hidden branch holding uncommitted changes. Restorable, and subject to gc.
git stash -u"stash"The variant that also takes files never added to git. The plain one skips them.
pop vs apply"the same"pop restores and deletes the entry; apply keeps it. Keep it when the restore conflicted.
working tree"my files"The live folder. The only place uncommitted work exists, and the only thing reset --hard can destroy.
destructive delete"the agent cleaned up"An agent removed a file it never read. Cheap to undo if committed.
thrash"it iterated"Multiple rewrites of one file in a turn. Only the diff shows whether it improved.
scope creep"it helped while it was there"Changes outside the ask. git status is the only reliable detector.

What this lesson skipped

Worktrees. The real answer to "I want two agents in two folders at once". The word contains "working tree" because it is exactly that — a second working folder on a second branch. Phase 16 is dedicated to it, and it is the right tool whenever the problem is concurrent work rather than interrupted work.

git stash workflows with rebase and pop conflicts. Stashing, rebasing, and popping onto a changed tree can conflict three ways. Recoverable, but rare — and the recovery is git stash list plus patience.

Hooks that block destructive agent commands. The tool exists (PreToolUse hooks intercept writes). It is Phase 08, and it depends on concepts this course has not established yet.

Check yourself

5 questions. Answer before you reveal — recognising the right option is not the same as being able to produce it.

  1. You stash, switch branches, come back, and the stash pop conflicts. What is the best next move?

    1. git stash pop again — it will merge the conflict away
    2. Resolve the conflict in the files, git add them, then drop the stash entry yourself
    3. git reset --hard to clear the mess and pop again
    4. git stash drop to get back to a clean tree and start the change over
    Show answer

    B. Resolve the conflict in the files, git add them, then drop the stash entry yourself

    A stash entry is a commit, so popping it back into a tree that moved underneath it can conflict. The conflict is now an ordinary merge conflict — resolve it, git add, and the entry is redundant. Popping again (A) would hit the same conflict. reset --hard (C) throws away the half-finished work the stash existed to protect. Dropping and redoing (D) is the only option that guarantees loss, which is the opposite of why you stashed.

  2. An agent deletes config.toml, rewrites a source file four times, and adds an unrelated formatter change. You stashed before the run. What do you reach for?

    1. git reset --hard, which undoes all three in one command
    2. git stash pop, which returns the agent's changes so you can review them
    3. git checkout -- config.toml, restoring only the deleted file and leaving the other changes visible
    4. Nothing — a stash only protects uncommitted work you made yourself
    Show answer

    C. git checkout -- config.toml, restoring only the deleted file and leaving the other changes visible

    Each shape wants a different response, and a targeted restore of the one destructive change leaves the other two visible for review — which is how you notice scope creep at all. reset --hard (A) discards all three including work worth reading. stash pop (B) would bring the agent's changes back in rather than remove them. And a stash records whatever the tree held at the time, including agent edits (D) — that is exactly why it is the right pre-run tool.

  3. An agent's final version of a file looks wrong, and you suspect it improved then got worse across the turn. Where is the evidence of that?

    1. Read the file carefully — the comments will show which parts are stale
    2. git diff against the commit you made before the run, which shows the net change and whether the whole file was rewritten
    3. Re-run the agent with the same prompt and compare the two results
    4. git log — the agent commits each step internally
    Show answer

    B. git diff against the commit you made before the run, which shows the net change and whether the whole file was rewritten

    The final file is one state; the diff against your pre-run commit is the record of what moved. If the diff touches the whole file, you know it thrashed. Comments (A) are unreliable here — an agent's intermediate state rarely left any. Re-running (C) is expensive and proves nothing about the run you are inspecting. And agents do not commit each step (D) — nothing about the loop implies per-step commits, which is the whole reason the pre-run commit matters.

  4. You stashed some work, then three weeks later you cannot remember what was in it. How do you look without destroying it?

    1. git stash show -p stash@{0}, which diffs the entry without applying or dropping it
    2. git stash pop, then read it and re-stash if it is not what you want
    3. git stash drop, which clears the list so the real entries are visible
    4. git reflog, which lists stashes as well as branch positions
    Show answer

    A. git stash show -p stash@{0}, which diffs the entry without applying or dropping it

    stash show -p is the read-only inspection: it prints the diff and leaves the entry exactly where it was. Popping (B) risks a conflict in a tree that moved three weeks ago and removes the entry on success. Dropping (C) is the one thing you definitely do not want when you are trying to find out what is in there. And the reflog is the branch-and-HEAD record — stash entries are not in it (D), which is exactly why git stash list and show exist.

  5. You stash work, then write three new files that were never added. You run a plain git stash and switch branches. What happened to the three files?

    1. They were stashed too, since stash captures the whole working tree
    2. They stayed in the working tree — a plain stash skips untracked files; git stash -u is the variant that includes them
    3. They were deleted along with the stashed changes
    4. They were committed to the stash's hidden branch automatically
    Show answer

    B. They stayed in the working tree — a plain stash skips untracked files; git stash -u is the variant that includes them

    A plain stash takes changes to tracked files only. Untracked files are not in git's object store, so they are not in the stash — which means they simply stayed put and are still on disk after the switch. That is the most common stash surprise, and the one that costs you an agent's newly created files. --stashed automatic commits (D) are not a thing, and nothing deletes them (C).