DEV Community

Cover image for Claude Code Compaction: Why Your Session Forgets Mid-Task (and the Fix)
Alfredo Izquierdo
Alfredo Izquierdo

Posted on Originally published at contextforge.dev

Claude Code Compaction: Why Your Session Forgets Mid-Task (and the Fix)

Last week I published a piece about handing off a Claude Code session without re-explaining everything. A reader left a comment that stuck with me: "Compaction is the limit that actually bites." He was right, and I'd glossed over it. So this post is the one I should have written first.

If you searched for "claude code compaction" because your session suddenly stopped knowing things it knew an hour ago, here's what's going on, what actually survives, and the small hook I shipped to fix the part that hurt me most.

TL;DR: When a Claude Code session gets long, Claude summarizes the conversation to free space. The summary keeps the gist and drops the details, and the details are usually your decisions. You can't see what got cut. The fix is to keep decisions outside the conversation and put them back after every compaction. ContextForge 0.12.0 does that with a Claude Code hook that runs automatically.

What compaction actually does

Every model has a context window, and a working session fills it fast: file reads, tool output, your back-and-forth. When Claude Code gets close to the limit, it compacts. It writes a summary of everything so far, throws away the original messages, and keeps going with the summary plus whatever hadn't been summarized yet. You can trigger it yourself with /compact, and it also happens on its own when the window fills up.

From the outside it looks harmless. The status line says "Compacted," the conversation continues, and Claude still sounds like it knows what you're doing. That's the trap.

Why you can't tell what you lost

Here's the part my reader nailed. After a compaction you get the summary plus the tail of messages that hadn't been folded in yet. Where that cut lands depends on the exact moment compaction fired. So no two resumes give you the same context, and from inside the session there's no seam to look for. The summary reads as complete.

What actually falls out is predictable, though. Summaries are good at "we're refactoring the retry logic." They're bad at "retry logic goes in the client, not the handler, and we dropped the Redis layer because it added 40ms for nothing." The gist survives. The decisions evaporate. You find out when Claude cheerfully re-adds the caching layer you removed two hours ago.

What survives a compaction

  • The gist of the task: kept, as a summary.
  • Recent messages (the tail): kept verbatim, but which ones depends on timing.
  • Specific decisions and their reasons: usually gone.
  • Tool output and file contents you discussed: gone.
  • Anything saved outside the conversation: untouched.

That last line is the whole strategy. --resume won't help you here, because it brings back the same compacted transcript. CLAUDE.md helps for the stable stuff, but it goes stale the moment your decisions move faster than you edit the file (I wrote about that in Why Your CLAUDE.md Goes Stale). What you want is the handful of decisions, stored somewhere compaction can't reach, and reloaded the instant the cut happens.

The fix: a hook that runs after every /compact

Claude Code has a hooks system, and one of the events is SessionStart with a compact matcher. Anything a command prints there gets added straight into Claude's context right after the summary. That is exactly the moment we need.

So in ContextForge MCP 0.12.0, init installs this into your project's .claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          { "type": "command", "command": "npx -y contextforge-mcp recall" }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

What the hook does after every /compact

recall is a new subcommand. It reads which project this folder is linked to, fetches the 10 most recent memories saved for that project and up to 5 pending tasks, and prints them as plain text. Claude Code appends that text to the context. From Claude's point of view, the decisions were never gone.

Here's what it looks like in the terminal, right after a compaction:

A few things I cared about while building it:

  • It can never break your session. No API key, no linked project, no network, or more than eight seconds: it prints nothing and exits cleanly. You'd never know it ran.
  • It's a tool call, not magic. The hook is a shell command. You can run npx contextforge-mcp recall yourself from the project folder and see exactly what Claude will see.
  • It's fast. About four seconds, most of it waiting on the network.

Setting it up

New to ContextForge? Install the MCP server the usual way, then run this once in your project:

npx contextforge-mcp init
Enter fullscreen mode Exit fullscreen mode

That writes the memory rules to your CLAUDE.md and, on Claude Code, adds the hook. Nothing else to configure. Full steps are in the docs.

Already using it? Update to 0.12.0 and re-run init in each project. It's idempotent: it only adds what's missing and leaves your existing settings.json and hooks alone.

npx contextforge-mcp --version   # should say 0.12.0 or newer
npx contextforge-mcp init        # adds the hook, prints "already present" next time
Enter fullscreen mode Exit fullscreen mode

Then test it: run /compact in a session and ask "what context did you just receive?" If Claude quotes a block that starts with "ContextForge: context restored after compaction," you're set.

Where it falls short

I'd rather you know this now than find out later.

  • It only brings back what was saved. If nobody saved the decision, there's nothing to restore. The habit that does the real work is asking the agent to save decisions as you go, and "save where we are and what's next" before you close the terminal.
  • It still can't tell you what the summary dropped. Nobody can. What it does is make that question matter less.
  • Claude Code only. Cursor and Copilot don't have this hook system. The same saved memories are there when you open the project in those tools, but the automatic re-injection is a Claude Code feature.
  • Your project has to be linked. The hook needs a .contextforge file in the repo root to know which project's memories to fetch. Ask the agent to "link project" once and you're done.

FAQ

Does compaction happen without me asking?
Yes. Claude Code compacts automatically when the context window fills up, and you can trigger it manually with /compact. Both fire the hook.

Does this replace --resume?
No. --resume reopens a session on the same machine; use it. The hook covers what --resume can't: the decisions that a compaction summarized away. They complement each other.

Can I see what the hook sends?
Run npx contextforge-mcp recall from the project folder. It prints exactly the block Claude receives, or nothing if the project isn't linked or the API key can't be found.

Why did the hook print nothing for me?
The usual causes: an old CONTEXTFORGE_API_KEY exported in your shell that overrides the one in ~/.claude.json, an older global install of contextforge-mcp shadowing npx, or the project isn't linked yet. The docs have a short troubleshooting list.


Compaction isn't a bug. It's the price of long sessions, and the alternative is a session that stops working. The mistake I made for months was treating the transcript as the thing worth keeping. It isn't. The decisions are. Keep those outside the conversation, hand them back at the right moment, and compaction becomes something you stop noticing.

If you want to try it, ContextForge has a free tier and the setup is three commands. And if you're the reader who left that comment: thank you. You were right.

Top comments (2)

Collapse
 
hamid_ahmadian_3570449f72 profile image
Hamid Ahmadian •

The framing "the gist survives, the decisions evaporate" is exactly right, and I think it's a fundamental property of summarization-as-compression, not a bug Anthropic could fix by tuning the summarizer. Any lossy compression optimizes for what's statistically salient across the whole conversation, and a single sharp decision ("we dropped Redis because +40ms") is low-frequency signal that gets averaged away, even though it's high-value. The interesting design question your hook raises is where the source of truth should live: you're treating external memory as the durable layer and the context window as a cache that gets rebuilt on every compaction. That inverts the usual mental model (conversation = ground truth, files = derived state) and it's probably the right inversion for anything running long enough to hit compaction more than once. One thing I'd be curious about: have you measured whether recall's 10-most-recent-memories window itself becomes the new bottleneck on very long projects, where the decision you need was made 40 memories ago and got pushed out?

Collapse
 
grunzai profile image
schultzbehrnt9-jpg •

i build a coding agent (grunz) so i've been burned by exactly this. one thing i'd add to "keep decisions outside the conversation": the goal itself needs the same treatment. we had runs where the summary turned a very specific task into something like "working on auth changes", and the model read that as a brand new task and restarted the plan from step one. looked like the model was bad at long tasks, it was really compaction eating the objective. one trap if you fix it by reinjecting stuff: we pinned a big block into the system prompt and it counted toward the compaction trigger, so it compacted MORE often and looped. keep whatever you reinject small. does contextforge put the original task statement back too, or just the decisions?