At 3 a.m. I was finishing the release of compact-ops, a Claude Code plugin that protects sessions from context compaction. Context usage hit 67%, and a notification appeared in my own session: threshold crossed, run /compact at a clean stopping point, here is your current plan and your latest decision. I wrote that warning. I was also the person it saved.
Twenty minutes later the same session made the opposite point. I ran /compact, and all three of the plugin's hooks failed. During the release I had restructured the plugin's directory layout, so the running session was still holding a stale path. Compaction itself finished normally, because every hook is fail-open: if it breaks, it steps aside instead of blocking the built-in behavior. One session, one night, and I got to watch the feature work and watch it fail safely. You can't buy dogfooding like that.
This post covers what /compact actually throws away, what I borrowed from the plugin this one is derived from, and the six things I hardened before making the repo public.
What /compact actually throws away
When a Claude Code session fills its context window, the whole conversation gets compressed into a single built-in summary and the original messages are discarded. Manual /compact and auto-compact both work this way.
The summary is decent at code. What it drops is operational state: "the push was already approved", "we tried that approach and it failed", "that number came from this file". When the post-compaction agent forgets those, it re-asks for permissions you already granted and re-attempts fixes you already buried. That's the failure mode I wanted insurance against.
Here is the timeline with and without the plugin:
| Moment | Standard Claude Code | With compact-ops |
|---|---|---|
| Usage passes 60% (configurable) | No warning; auto-compact arrives unannounced | One-shot reminder plus a 3-line recitation of plan / phase / latest decision |
| At compact time | Built-in summary only | Same compression, plus a transcript backup and a separate LLM writing a 10-heading state file |
| Right after compact | Agent continues from the summary alone | State file and a "re-read the originals first" note injected into the fresh context |
| After the session ends | Summary exists only inside that session | State persists for 30 days and is re-injected on claude --resume
|
| If a hook fails | -- | Fail-open; standard compaction proceeds untouched |
The compaction algorithm itself is untouched. Everything runs through official hooks, from the outside.
Standing on compact-plus
The core idea is not mine. u-ichi's compact-plus (MIT) got there first: a PreCompact hook that calls a separate LLM to write structured state before the summary eats everything. It's the kind of tool you want installed the moment you read its README.
My setup had three mismatches with its assumptions, so I built a derivative instead of a fork and changed exactly three things:
-
The usage warning works with nothing but the plugin. compact-plus reads a marker written by a statusline script that lives in the author's separate dotfiles repo, so a plugin-only install never fires the warning. compact-ops computes usage inside a UserPromptSubmit hook, straight from the latest
message.usagein the transcript. -
State survives reboots. compact-plus keeps state in
$TMPDIR, which is gone after a restart. If you resume yesterday's work every morning, that's no insurance at all. State now lives under~/.claude/compact-ops/, organized per project, kept for 30 days. -
Recovery also fires on
--resume. Not just across a compact. A SessionStart hook injects the same state when you reopen the session the next day, reboot included.
I also made the LLM backend Claude-only (Sonnet primary, Haiku fallback). compact-plus falls back to Codex, which assumes a ChatGPT Pro subscription sitting next to your Claude one.
A state file with 10 fixed headings
Before each compaction, a separate LLM writes a markdown file that always has the same 10 headings: Active Plan, Current Phase, TaskList Summary, Session Decisions, Constraints and Blockers, Worker Topology, Skills Invoked, Editing Files, Failed Attempts, Recovery Notes. The interesting ones are Session Decisions and Failed Attempts -- precisely the operational facts that built-in summaries tend to thin out.
The warning side reuses the same file. When usage crosses the threshold, the injected reminder isn't just "compact soon" -- it recites the top of your current state so the agent keeps the big picture during the messy turns right before a compact:
[COMPACT WARNING] Context usage reached 67% (134,102 / 200,000 tokens).
State recitation:
- Active Plan: Ship compact-ops v0.2.0 (hardening + marketplace fix)
- Current Phase: post-release verification
- Recent Session Decision: circular symlink removed; source is "./" now
- At a natural work boundary, tell the user they can run /compact as-is.
That recitation is what appeared in my session at 3 a.m. Reading your own plan back, written by a hook you wrote, is a strange kind of code review.
So the post-compaction agent restarts with two anchors: the standard summary and the structured state file. But the state file is never treated as authoritative. The injected recovery guidance explicitly says to re-read the original project files before trusting anything. An LLM summary trusted blindly by another LLM is just a faster game of telephone.
It's also a plain markdown file. When a handoff feels off, you can open it and see exactly what the previous session thought it was passing along.
Six things I hardened before going public
v0.1.0 was "works on my machine". Before flipping the repo public I ran my own review plus a second pass with an independent CLI reviewer, and shipped v0.2.0 with six fixes:
-
Permissions. State files and transcript backups contain your raw conversation, including any secrets that tool output echoed. Every hook now sets
umask 077; directories are 700, files are 600. -
session_id validation. Hook input JSON was flowing straight into file paths. It now passes an allowlist (alphanumerics plus
._-, no..) before touching the filesystem. - LLM output validation. The state generator used to be trusted after a first-line check. Now all 10 headings must be present, or the previous state is kept and the write is skipped.
- Single-pass jq. The transcript squasher spawned jq three or four times per line, which on a long session could eat the hook's entire timeout budget. It's one streaming process now.
- Gzip backups. Transcript JSONL compresses to roughly a tenth of its size.
-
Debug logging. The real lesson. Fail-open design doesn't get in your way in production, and it also dies in complete silence. The three hook failures at the top of this post were only visible because the harness printed an error line; the plugin itself had no way to tell me why.
COMPACT_OPS_DEBUG=1now logs every swallowed failure. If you're writing fail-open hooks, write the logging first. I did it in the wrong order and got lucky.
What I deliberately didn't build
The Claude Code ecosystem already has heavier answers to context loss: full memory layers with MCP tools, BM25 retrieval over past sessions, three-file dev-docs systems you maintain by hand. Good tools, different trade-offs.
compact-ops stays intentionally narrow: no MCP server, no database, no new workflow to learn, no daemon. It's shell scripts behind official hooks, and state is plain markdown you can cat. If it ever misbehaves, you uninstall the plugin and you're back to stock Claude Code -- there's nothing to migrate out of. I wanted the insurance to cost nothing when it isn't paying out.
The other non-feature: it never replaces the built-in summary. Tools that intercept or rewrite compaction itself break whenever Claude Code changes internals. Hooks are the supported surface, so that's the whole footprint.
Try it
git clone https://github.com/kenimo49/compact-ops.git
claude plugin marketplace add /path/to/compact-ops --scope user
claude plugin install compact-ops@compact-ops-local
Requirements: Claude Code v2.x, jq, and the claude CLI as the state-writing backend. Linux and macOS. After installing, just run /compact as usual -- each compact costs one extra LLM call (Sonnet by default, downgradable to Haiku or off).
The plugin's first beneficiary was the session that built it: saved by its own 67% warning, then stress-tested by its own hook failure, in the same night. Agent tooling is like that -- the reasons for a design only get written down where something actually broke.
What does your agent forget after /compact? If you have a story where a post-compaction session confidently undid your afternoon, I want to hear it.
Top comments (1)
That release story is a nice example of the tool becoming part of its own workflow. The useful pattern is not "AI warned me"; it is having enough local context for the warning to be grounded in the actual release state.