Writing | | 10 min read

By his own hair

Everyone solved session amnesia. Nobody enforced who owns the handoff.

It is my night with Olivia. Every other night I sleep in my five-year-old's bed, because she is five and she needs someone in the room, and tonight is one of mine. She went under an hour ago. So I have done the thing you are not supposed to do: found the floorboards that do not creak and snuck out of my own daughter's bed to go and write code. The rest of the house is sleeping. Mimi and Mae, eight and ten, Stef, all of them down. I am the only one awake, one earbud in, because this hour I steal after everyone goes under is the only solo time a dad reliably gets, and there is a project I want to finish in it.

In the earbud is Khruangbin, White Gloves II, off a record called The Universe Smiles Upon You II. Brushed drums going round and round, a guitar jangling in circles somewhere between Texas and Lagos, and underneath all of it a bass walking calmly around the block, giving the circle a floor to stand on. Hold onto that picture. It turns out to be the whole post.

The project is small, the bug in it is stupid, and I love it. A session finishes a chunk of work and leaves a note for whoever comes next. The note should say who wrote it, so the next session can tell whether it is reading its own running record or trampling someone else's. There is one problem. To write the note that proves who it is, the agent needs the one fact it does not have. It cannot read its own session id. It does not know its own name.

Most tools in this space solve forgetting. Capture state before compaction, restore it on the next launch, survive the context window. That problem is well covered and mostly solved, and it is not the one keeping me up. The one keeping me up is the failure nobody enforces against: two sessions writing over each other's notes, and neither one noticing until the context you needed is already gone.

Engraving: Baron Munchausen sunk to the waist in a swamp made of overlapping handoff documents, gripping his own hair while a red ribbon is lowered to him by a hand reaching in from above the frame.
The Baron escapes a swamp by his own hair. The agent cannot: the hand that hauls it out reaches in from outside the circle.

The clobber

Here is how it bit me. Solo dev, but rarely a single session. An interactive one in the foreground, background ones grinding through their own work, and the whole thing spread across git worktrees so the parallel sessions could not step on each other's code. The worktrees did exactly that. The handoffs got clobbered anyway. The convention was one handoff file per branch at a known path, and it had held fine for months, which is the kind of thing that makes you stop watching it. The name keyed on the branch and nothing else. So when two sessions sat on the same branch, which on a solo repo is most of them, they wrote to the same handoff-main.md, separate worktrees or not. The worktree isolated the code and left the note exposed. The second write won. The first session's record, the part that listed which theories had already been ruled out, was simply gone. The next session opened the survivor, inherited half a picture, and walked straight back into a dead end I had already cleared.

I wrote about the downstream cost of that already, in Memory you can't trust: five sessions chasing a bug because each one inherited a clobbered handoff. The conclusion there was that the fix was not a better handoff template. It was making bad handoffs structurally impossible. This is the mechanism that does it.

Engraving of a scriptorium: two scribes hunched over a single shared sheet headed 'handoff-main', the second scribe's ink spreading as a red blot over the first scribe's faded lines.
Two sessions, one sheet. The second scribe's ink blots out the first, and no one notices until the lost lines are needed.

Manners don't hold

The public handoff tools are good, and they converge on the same shape: a markdown template, a hook that fires on compaction to save state, a hook on startup to load it. The best of them name the concurrency problem directly. Semih Erdogan's handoff is explicit that there should be one active handoff per branch and topic, that a bare undated file at the repo root is not to be trusted, that the hard part is quickly proving a handoff is yours, current, and actionable.

Stef and I run a handoff protocol of our own. Whose night it is with Olivia, who is on if someone wakes at 3am, who already packed the lunchboxes: we keep it straight the way two people in one house do, by talking, by a look across the kitchen, by the ambient sense of where the other one is. The manners hold for us because there are two of us and we can see each other. They do not hold for two processes that cannot.

Naming the problem is the right instinct. But every one of these tools enforces the fix with manners. The file is read-only by convention. The naming scheme is unique by convention. Nothing stops a second session from writing the same path. Convention is exactly the wrong layer for a hazard that is high frequency and low salience, the kind of thing you do correctly four hundred times and wrong on the four hundred and first without noticing. I argued that point separately in Discipline doesn't scale. Either make the hazard impossible or make failure loud. Asking the agent to remember is neither.

The marker is the lock

So the enforcement is not a manner. It is one line at the top of every handoff file:

<!-- claude-session: 9e0d3802-4f2a-... -->

There is no sidecar lock file. The lock lives inside the artifact, so ownership travels with it through git, across machines, through a mv. A PreToolUse hook reads the calling session's id and compares it against the marker in the content being written and the marker already on disk. Two short regexes carry most of the weight:

const VALID_GRAMMAR = /^handoff-[a-z0-9]+-[a-z0-9]+(-[a-z0-9]+)+\.md$/;
const MARKER_RE     = /^<!--\s*claude-session:\s*(\S+)\s*-->/;

If the on-disk marker belongs to another session, the write is blocked before it lands. Not warned. Blocked. The grammar regex does a second job: it forbids the bare handoff-main.md that caused the original collision, by requiring a branch plus a topic of at least two tokens. You cannot accidentally write the colliding filename, because the colliding filename is rejected on sight.

The grammar forces a topic. It cannot force a good one. Two sessions on the same branch stay apart only if their topics differ, and they collide right back if both reach for handoff-main-fix.md. So the rule kept evolving past the regex: a topic of at least two real words, stopwords stripped, and a check that compares the leading tokens of the sibling slugs, so a session naming its work workbench-bug is told that workbench-fix is already live and probably the same workstream. Grammar stops two names being identical. The semantic check stops them meaning the same thing. None of it was designed up front. Every clause is a clobber that already happened.

Out of the swamp

The marker is the whole mechanism, and the agent cannot produce it. The model has no native access to its own session id. So the requirement is the circular horn of the trilemma made concrete: to write the handoff it needs its id, and it gets its id by writing the handoff. Left there, it sinks.

It does not escape the way the Baron claims to. It cannot pull its own hair, and nothing inside the loop can lift the loop. The hand has to come from outside the circle. I know the shape of this because most nights I am the hand: a five-year-old cannot fall asleep alone, cannot get herself out of the dark by her own effort, so someone climbs into the bed from outside and becomes the fixed point she cannot be for herself. Tonight I climbed out of that bed to write a hook that does the same favor for an agent. The bass is still walking around the block under the circling guitar, giving it a floor. The escape is the third horn of the trilemma, an axiom asserted from outside the circle. The first write is made to fail on purpose, and the failure hands over the missing fact:

Handoff write missing or wrong ownership marker.

Your session_id: 9e0d3802-4f2a-...

Prepend exactly this as line 1:
  <!-- claude-session: 9e0d3802-4f2a-... -->

Then retry.

The agent reads its own id out of the block reason, writes it into line one, and retries. The hook reached in and handed it the strand. One block per fresh handoff, and from that point the file is self-identifying for every session that ever touches it. The capability the model lacks becomes a one-time handshake instead of a permanent hole. The error is not a failure mode; it is the ground the agent stands on to climb out.

A three-panel scientific plate titled Trilemma Munchhusianum: REGRESSUS, an endless colonnade; CIRCULUS, an ouroboros enclosing a trapped figure; AXIOMA, a hand from a cloud setting a keystone, with a red thread running from the trapped figure out to the keystone.
Three horns. The agent is trapped in the circle; the red thread leads out only to the axiom asserted from outside it.

The model will route around you

A guard that only watches the file-writing tool is a guard the model steps over the moment that tool is inconvenient. Blocked on Write, it will reach for a shell redirect. So the hook covers every surface a file can be mutated through: Write checks the marker in the new content, Edit checks the marker on disk, and Bash matches redirects to handoff paths.

const BASH_WRITE_RE =
  /(>{1,2}|tee\b|sed\s+-i)\s*[^|;&]*?(\S*?\/memory\/handoff-[a-z0-9-]+\.md)/;

Writing the test suite for this is what caught the most embarrassing bug in the whole thing. The hook accepts two client schemas, Claude Code's Bash and the Gemini CLI's run_shell_command, and the path-extraction handled both. But the line that actually blocked the redirect checked only one of them:

// before: dead for Claude Code, matched only gemini's tool name
if (tool === 'run_shell_command' && notOwned) block(...)

// after: covers both client schemas
if ((tool === 'run_shell_command' || tool === 'Bash') && notOwned) block(...)

For the active client, the entire shell-redirect defense was dead code. It extracted the path, examined it, and waved the write through. The convention-only tools I had just finished criticizing at least fail where you can see it. Mine failed silently, in the exact spot that was supposed to be airtight, and the only reason I know is that I wrote a test that drove a real Bash payload through the hook and watched it sail clean past the block. A guard you do not test is a guard you do not have.

A Vauban-style fortification plan titled Three Gates, One Wall: a citadel labelled handoff with gates WRITE and EDIT bolted shut, and a BASH gate that was a breach in the wall now being bricked up, a red bolt being driven home.
One citadel, three gates. The Bash gate was a gap in the wall standing open; the bolt is only now driven home.

What it doesn't catch

This makes the unaware clobber impossible. It does not make the chosen one impossible, and the distinction matters more than the headline. A session that is correctly shown a foreign marker can still archive the file or set a bypass environment variable. The hook prevents the session that does not know a conflict exists; it cannot prevent the session that knows and overrides anyway. The backstop for that class is a human reading the visible block message, not the hook.

There is a real race, too. If two sessions create the same brand-new filename in the gap between the hook reading the disk and the tool writing, one still loses. For a solo dev it is vanishingly rare, and a lock would cost more than the failure does, so it stays documented rather than fixed. And the whole thing fails open: any internal error in the hook exits clean and degrades to convention rather than bricking the session. The escape hatches exist on purpose, because a guard you cannot override is a guard people rip out. Every bypass is logged so the disabling is at least visible.

Ownership, not amnesia

If session persistence lands natively in these tools, and the open feature requests suggest it might, it will solve the forgetting. It is unlikely to solve the ownership, because ownership is a property of who is writing, not of what is remembered. That is the layer this sits on, and the reason it is worth keeping separate from the continuity story everyone else is telling.

An engraving of a two-pan balance: the AMNESIA pan holds a blank page and rises; the OWNERSHIP pan holds a page stamped with a red wax seal reading claude-session and sinks. A signet ring rests on the table beneath.
Amnesia asks what was remembered. Ownership asks who is writing. The marker is the signet that answers the second question.

The hooks, the skill, the test suite, and the convention are open source, anonymized, MIT, at claude-handoff-guard. The interesting line in the whole repository is still the one where an agent learns its own name by being told it cannot write yet.

I pushed the branch and crept back into Olivia's bed before it went cold. The agent had learned its own name. She slept through all of it.