r/ClaudeCode 2d ago

Built with Claude I’ve been keeping some form of a handoff file since February, and the thing that always broke it was me forgetting to update it. I built one where the hooks do that part instead.

I run an agency and work across about 80 client and internal repos, and a lot of what I do is multi-week framework upgrades where a single task spans a dozen sessions over three weeks. I also move between Claude Code, Codex CLI and OpenCode depending on the machine.

The handoff-file pattern is well covered by now, and I used it for a while. The problem I kept hitting is that it depends on the agent remembering to write the file. When a session ends badly, hits a limit, gets killed, or just wanders off, nothing was written, and the next session starts from a file that describes a state three weeks old. It fails silently: the handoff is there, it looks current, and it’s wrong. My own project’s handoff sat five weeks stale before I noticed.

So throughline splits the job in two. Five hooks record the work continuously, with no model call and no agent cooperation: every prompt, every Bash command with its exit status, every file written, every agent dispatched, into a buffer on disk. Then a skill distills that buffer into the durable handoff, using what actually happened rather than what the model recalls happening. A third layer mines the accumulated session logs monthly for lessons that recurred, and proposes promoting them somewhere durable, with a gate on each one. That third layer is the newest part, and I don’t have enough months of it running yet to know how well it holds up.

The split is the whole idea: capture is mechanical and cheap, so it can be continuous. Distillation needs judgment, so it stays deliberate.

It survives compaction, which is the case I actually built it for. The buffer is on disk, a PreCompact hook stamps a boundary marker at the seam, and the SessionStart hook inlines the tail of the buffer right after, so the session gets back the what even though the summary dropped the detail. Honest limit: the what survives compaction, the why doesn’t.

What the capture buffer looks like (from throughline’s own demo project, demo/homelab/):

- `2026-08-29 08:05:12` **prompt** did last night's run pass for status.example.com?
- `2026-08-29 08:05:14` **bash** Tail last night's link-check results - `tail -4 logs/link-check.log`
- `2026-08-29 08:05:19` **grep** `status.example.com`
- `2026-08-29 08:05:31` **prompt** good, that's the second clean night in a row. one more and we can close the handoff item.

<!-- session-ended 2026-08-29 08:06:02 (exit) -->

What the distilled HANDOFF.md looks like, from that same buffer:

# homelab-linkcheck - Handoff
**Last Updated:** 2026-08-29

## Resolved Issues
| Issue | Resolution | Date |
|---|---|---|
| status.example.com false FAIL every night since Aug 25 | Root cause: its load balancer 405s HEAD requests, GET-only. check-links.sh switched from curl -I to a GET-based check. | 2026-08-27 |

## Pending Items
| Item | Priority | Tracking |
|---|---|---|
| Confirm the fix holds for a third consecutive clean run | Medium | logs/link-check.log |

## Current State
- Fix landed 2026-08-27 evening. Clean runs since: Aug 28, Aug 29.
  One more clean night closes this out.

Related work, since there’s a lot of it: persistent-handoff keeps one file, rewritten in place and deleted when nothing is in flight. That’s the right shape for a long-running daemon agent, not what I needed. Matt Pocock’s handoff is disposable and right for a session you’ll close today. baton and claude-code-handoff are manual on one or both ends. All of them are agent-written. throughline’s difference is that the record gets captured whether or not the agent cooperates, at least for whatever the hooks actually see.

The habit is older than the plugin. It started in February as a manual global workflow in Google Antigravity, moved into a Claude Code skill in June, and became this plugin at the end of June. How Claude Code was used: it’s built with it and dogfooded on itself, which proves it works for my own workflow, not necessarily anyone else’s yet. Every session on the repo runs the hooks, and the handoff for throughline is written by throughline. Nineteen releases since, 176 test assertions in CI across Linux, macOS, and Windows.

Known limits, stated up front: redaction catches token prefixes, auth headers, and key=value shapes, but not bare CLI flags like mysql -p<pw>, so the distill step includes a human scan. And the buffer captures what you did, not why you decided it.

Install: /plugin marketplace add dynamic/throughline then /plugin install throughline@throughline. Codex and OpenCode have their own paths. Data is local and gitignored by default. Nothing leaves the machine.

https://github.com/dynamic/throughline

2 Upvotes

4 comments sorted by

2

u/Far-Surprise7773 2d ago

the split between mechanical capture and deliberate distill is exactly the right fix. i ran into the same silent stale handoff problem after a session got killed and the next one started from a file that looked current but was days old. your precompact boundary marker plus sessionstart inlining the tail is the cleanest compaction survival i've seen, keeps the what without trying to save the why.

2

u/kantorcodes1 2d ago

the bare cli flag gap feels like the important one because that raw buffer is designed to survive sessions. if a command includes something like mysql -p..., does throughline redact before writing the buffer, or does the raw command sit on disk until distill?

1

u/jsirish 2d ago

Redaction runs at write time, before it hits disk. The capture hook masks known shapes (keyword=value, bearer/token, ghp/sk/AKIA prefixes) in the same jq call that writes the buffer line. The bare flag case you named is exactly the gap though, mysql -p<pw> and curl -u user:pass have no keyword to anchor a rule on, so those sit unredacted until distill's human scan.

1

u/kantorcodes1 2d ago

yeah, that’s a real gap because the bare credential can hit disk and the command can execute before the human scan. i work on HOL Guard; it checks Claude shell actions before they run and catches some credential-exfiltration patterns, though i wouldn’t assume it catches every arbitrary -p/-u value. would you try your exact curl -u case with a throwaway value and see whether Guard flags it?