Your cleanup handler did not run, and you will never be told
There is an incognito mode that runs a session with a blank memory. The implementation is the obvious one:
- move the real memory aside into a stash
- point the memory slot at a throwaway directory
- on exit, put the real one back
Step three was a `trap … EXIT INT`. That is the shape every shell script uses, it reads as correct, and it is what I would have written on a whiteboard.
Then you close the terminal window.
What closing a window actually does
The terminal sends `SIGHUP` — hang up, a name inherited from modems dropping the line. The default disposition of `SIGHUP` is to terminate the process, and it terminates it **without running the `EXIT` trap**, because the trap was never registered for that signal.
So the sequence stops after step two. Which leaves:
- the memory slot pointing at a temp directory that the OS will delete
- the real memory sitting in a stash nobody is going to come back for
- and, once the temp is gone, a dangling symlink
Ask the tool for its status and it reports the slot as empty. Not corrupt, not missing — **empty**, which is a perfectly ordinary state for a slot to be in, so nothing anywhere raises an alarm.
`SIGTERM` had the same hole. Anything that shuts the machine down politely would have done it too.
The fix that is not the fix
The narrow repair is one line: catch `HUP` and `TERM` as well as `EXIT` and `INT`. That is correct and you should do it.
It is also not enough, and the reason is the whole point of this post.
**`SIGKILL` cannot be trapped. Neither can the power going out.** No signal handler is reached when a laptop's battery dies mid-session, when the OOM killer picks your process, or when someone runs `kill -9`. If your correctness depends on a cleanup handler running, then your correctness has a set of conditions under which it is simply absent, and those conditions are not exotic — a dead battery is a Tuesday.
There was a nastier detail in this specific case. The stash-and-restore only self-healed on the *next incognito launch*. An ordinary session would start, find the dangling link, and carry on with an empty memory forever. So the failure did not just happen — it **persisted**, silently, until someone happened to use the same rare feature again.
Move the repair to startup
The second guard is the one that matters: **a check at every launch that heals a dangling incognito link, whatever caused it.**
That covers the closed window. It also covers `SIGKILL`, power loss, a crash inside the tool itself, and every future way of dying that I have not thought of — because it does not ask *why* the state is broken, only whether it is.
The general form, which applies well beyond shell scripts:
Cleanup handlers are an optimisation: they make the common case tidy. **Startup checks are the guarantee**: they are the only thing that runs after the ways of dying that skip your code.
If you have ever written a lockfile that goes stale when a process is killed, you already know this shape. The answer was never a better trap. It was that the next process to start has to be able to look at the lockfile and decide, on evidence, whether its owner is still alive.
The checklist I would keep
- `trap … EXIT INT HUP TERM` — not just `EXIT`. Closing a window is `HUP`, and it is the most common way a person ends a session.
- Assume the handler will not run. Ask what state the system is in if it does not.
- Put the repair on the **entry** path, not only the exit path, and make it unconditional and cheap enough to run every time.
- Make sure the broken state is *detectable* — a dangling symlink is, which is why this was fixable. A slot that reads "empty" when it should read "broken" is how it stayed hidden.
The boring footnote
The flag is `--ephemeral`, in clikae — plain bash, MIT, no daemon, no telemetry. It is also the tool I use to review my own diffs with a reader who has no memory of them, which is how a feature for throwing sessions away turned into the most useful thing in the box.
Keep reading
-
The day my own cleanup tool deleted 612 MB of live work
A cleanup feature ate a session that was still open, on my own machine, the day it shipped. Three independent guards had to fail for that to happen — and the deepest one was that the tool was showing a machine's guess instead of the name I had typed myself.
-
Your best reviewer is the one who has never heard your reasoning
A bug survived five releases because everyone who read that line already knew what it was supposed to do. The fix was not reading harder — it was pointing a reader with no memory at the diff, and paying the tax that comes with one.
-
The half they rent you, and the half that is yours
The model is rented and the vendors are at war over it, which is good news. The other half — who you are, what you know, where you left off — is yours, and it only stays yours if it can leave.