Workflows Never lose finished work
The work you shipped and never saved
On 20 July I went looking for how much of this site was actually backed up. Most of it was not. Fifteen files were live on the internet, serving real visitors, and existed on one laptop and nowhere else. This is what caused that, and the two small scripts that make it impossible to repeat.
- What it found
- 15 files live and saved nowhere else
- Cost
- $0. Two Node scripts, no dependencies
- Time to wire up
- About 10 minutes, once
- How often it interrupts
- Once per session. Never twice
What the audit found
One commit to fix it: 24 files, 3,258 lines. Fifteen of those files were live at the time. A page that was already serving visitors. A second page and the function behind its form. The fonts and the animation libraries that second page needs to render at all. Of the other nine, seven were a test, some design work, and a page that has never been public, all of which also existed in exactly one place. The last two were files already being tracked properly.
All of it built. Most of it deployed and working. None of it saved anywhere but the machine it was written on. A dead drive that week would have taken back weeks of finished work, and I would not even have had a list of what was gone.
Those figures came back out of git history rather than out of memory, but this repository is private, so you cannot open the commit and check me on them. Fair reason to weigh this as a story rather than as evidence.
Nobody was careless. That is the whole point.
The tempting read is that somebody forgot. That read is wrong, and it is worth being precise about why, because the same four conditions are probably sitting in your setup right now.
- Nothing ever said to commit. Every instruction file I had written described how to build a thing and how to publish it. Not one of them described how to save it. The step was missing from the definition of done, so it was missing from the work.
- No folder feels like the current one. I start sessions from my home directory rather than from inside a project. When your working directory is the parent of nineteen repositories, none of them is "the" repository, and the habit of glancing at the state of the one you are in never forms.
- Publishing feels like saving. It is not. A file can be live, indexed, and taking real traffic while existing in exactly one place. Those two facts are unrelated, and the first one is reassuring enough to hide the second.
- The one rule I did have pointed the wrong way. I already had a rule about untracked files, written after an earlier incident where a publish command swept up files nobody meant to ship. It treated untracked files as a hazard to route around. So untracked work got carefully avoided instead of committed.
Every session did its job and left the work on the floor. Which is the actual lesson: a rule that lives only in a document is invisible to anything that does not happen to read it that day. If you want a habit, write something that runs.
The fix is two scripts, and only one of them does anything you can see
Claude Code will run a script of yours at defined moments in a session. Two moments matter here.
| Script | When it runs | What it does |
|---|---|---|
track-edits |
After every edit and every shell command | Writes down which project folder that edit landed in, to a list for this session. Never blocks, never complains, never fails loudly. |
uncommitted-check |
When the session tries to end | Reads that list, looks only at those folders, and stops once if work this session wrote is uncommitted, or committed and never pushed. |
The first one is the part people skip, and it is the reason this check survived past its first week.
Without the recorder, the check has to report every changed file in the folder. A project is untidy for all kinds of reasons that are none of the session's business: you have a file open, a scheduled job wrote something, another session is running right now. Report all of that and you teach the agent two bad habits, committing work it did not write and does not understand, and treating the whole check as noise to click past.
So the recorder exists to make the interruption specific. The check says these are the files you wrote and they are not saved, and everything else in the folder is filtered out. Specificity is the difference between a check you keep and a check you turn off.
One honest limit. A shell command does not tell the recorder which file it touched, only which folder it ran in. So any project where the session ran a command falls back to reporting every changed file in that project, and in practice that is most sessions. The filtering is exact for edits and coarse for commands. That is the part of this worth improving next, and it is written in the file as a known tradeoff rather than left as a surprise.
What the interruption has to say
"You have unsaved files" is not enough to act on. The message names the four routes and asks for a decision on each folder, because the right answer genuinely differs:
- Work from this session, finished or not? Commit and push it. The message carries the exact command, so there is nothing to look up.
- Push rejected? Usually something else pushed to that remote first. Pull with a rebase and push again. Never force.
- Generated output or a local scratch file? It belongs in the ignore list, and that change gets committed too, or the next session sees the same thing and makes the same decision again.
- Not yours, and you cannot account for it? Say so and leave it alone. Committing changes you did not make and do not understand is worse than leaving them.
One more line that earns its place: read the diff for secrets before committing. The fastest way to turn a backup habit into an incident is to sweep a token into a public repository on the way to being helpful.
It gives up on purpose
The check blocks a single time per session and then lets go for good.
One reminder is a reminder. Two is a trap. A folder can be untidy for reasons the agent cannot resolve at all: a merge conflict, a file you are holding open, an artifact that should be ignored but is not yet. A check that refuses to release would strand the session with no way to finish, and the only available fix would be to remove the check.
So it fires once, and if the session finishes anyway it has to say in plain words what it left and why. That turns the shortcut into a decision on the record instead of a silence. Everything else about it fails open too: unexpected input, a missing tool, any error at all, and it steps aside rather than standing in the way. A check that can refuse you needs a way to be wrong.
Take it
Both scripts are public, MIT licensed, plain Node with nothing to install. Two more from the same setup are in there as well, including the publish check that came out of the earlier incident mentioned above.
- The hooks repository The hook files, each opening with a comment explaining why it exists. For several of them that is the incident it came out of.
- The event that fires after an edit Where the recorder is wired in, and what it is handed when it runs.
- The event that fires when a session ends Where the check is wired in, including the flag that stops it firing twice.
Why this shape
The obvious answer to losing work is a backup tool. That would have copied the laptop, including the parts I did not want copied, and it would have told me nothing about what was finished versus abandoned. The problem was never storage. It was that finishing had no definition, so work got published and then let go of.
Wiring the missing step to the moment it is actually missed costs two files and interrupts about once a week. It is a much smaller thing than the backup tool and it fixes the part that was broken.
Both of these live in one settings file
Hooks are wired up in the same file that decides what Claude can do without asking you first. The free settings builder writes that file for you from a few plain questions, with nothing to install.
Build your settings file →