Context Compaction: How to Control What Your Coding Agent Keeps
Compaction runs on a threshold you set, summarizes what it guesses matters, and re-reads a short list of files. Each of those three is a control you can aim.
Before your next long Claude Code session, decide what should survive its next compaction. You can set the token threshold, tell the summarizer which decisions to preserve, and arrange for a working file to be read back afterward. Each control solves a different part of the problem, and none guarantees that every detail will survive.
The usual advice is to write things down, which is correct and not very actionable, because it does not say what to write, where to put it, or what reads it back. This piece is about those three controls. For the separate question of whether you are losing context mid-session or starting cold in a new one, which have different fixes, see why coding agents forget between sessions.
Compaction summarizes the conversation and reloads the files
Two things happen when a long session compacts, and only one of them is a summary.
Claude Code's documentation describes the order: "It clears older tool outputs first, then summarizes the conversation if needed. Your requests and key code snippets are preserved; detailed instructions from early in the conversation may be lost." Then it rebuilds. According to the context window documentation, the project-root CLAUDE.md, unscoped rules, auto memory, and the plan written in plan mode are re-injected from disk rather than summarized, and Claude Code "re-reads up to five of the files Claude has read or edited in the session, choosing the ones modified most recently."
That reload is the part worth designing around. Anything on disk that the harness opens again arrives at full fidelity. Anything that only ever existed as conversation arrives at whatever fidelity the summarizer chose. The controls below work on both halves.
The specifics here are Claude Code's, and the commands, settings, and hook names below are its own. Other agents compact too, and the triggers and the reload behavior differ by tool, by model, and by version, so check the documentation for the agent you run. The shape of the problem carries over even where the mechanism does not: state you wrote to a file can be re-read, and state that only existed in the conversation cannot.
When the threshold itself is the problem, move it. /autocompact 500k sets how full the window gets before the automatic pass runs, and the model configuration documentation puts the accepted range at 100K to 1M tokens, capped at the model's own window. Models running with a native 1M window compact at about 967K by default, and Sonnet 4.6 and Opus 4.6 without extended context compact at the 200K boundary. A later threshold buys you a longer run before the first summary, and changes nothing about what that summary keeps.
Tell the summarizer what to keep before it runs
The automatic pass has no signal about which parts of the session mattered. You can give it one in two places.
The first is standing instruction. Claude Code's documentation says to "add a 'Compact Instructions' section to CLAUDE.md" to control what is preserved during compaction. Because the project-root CLAUDE.md is re-injected from disk after every compaction, a rule you put there survives the event that it governs.
The second is per-compaction. Running /compact with a focus, such as /compact focus on the auth bug fix, produces a summary built around what you named. The documentation is direct about the tradeoff: "The summary keeps what you choose instead of what the automatic pass guesses is important." Reach for this at a task boundary, before you start something new, rather than waiting to be interrupted mid-step.
Write compaction instructions as a list of things to carry, not a request to be thorough. The goal, the next step, the decisions that have to hold and why, the approaches already ruled out and the reason each was ruled out, the failing tests. Rejection reasons earn their place because they are the detail a summary drops most readily and the one whose loss you notice last.
Summarize the part of the session you are finished with
Compacting the whole conversation to free space is a blunt instrument when only the first half is finished.
/rewind lets you pick a point and compress one side of it. Per the checkpointing documentation, Summarize up to here compresses the conversation before the selected point while "keeping later messages intact," and Summarize from here compresses everything from that point forward.
Which one to reach for depends on where the expendable stretch sits, and it is easy to pick the wrong one. The documentation's own example is a verbose debugging session: select the point where it began and choose Summarize from here, which the docs describe as "keeping your initial instructions intact." Choose Summarize up to here at that same point and those early instructions are inside the compressed range, because it compresses everything before your selection rather than only the stretch you had in mind.
You can steer these too. Highlight a summarize option and type instructions where the row reads add context (optional) before pressing Enter.
One detail changes how you should think about all of this. Summarizing "doesn't change files on disk, and the original messages stay in the session transcript, so Claude can still reference the details." The messages are compressed out of the request, not destroyed. What you lose is automatic access, not the record.
A hook puts the same facts back after every compaction
Everything above depends on you being present and remembering to act. A hook does not.
Claude Code fires SessionStart with a compact matcher after compaction, and the hooks guide describes the pattern plainly: "Use a SessionStart hook with a compact matcher to re-inject critical context after every compaction." Plain text the command writes to stdout is added to Claude's context.
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "cat \"${CLAUDE_PROJECT_DIR}/WORKING.md\" && git -C \"${CLAUDE_PROJECT_DIR}\" log --oneline -5"
}
]
}
]
}
}
The command can be anything that produces output, which is what makes this useful. Anchor the paths rather than writing cat WORKING.md, because hook handlers run in the current directory and that moves as Claude works. ${CLAUDE_PROJECT_DIR} is "the project root where the session started," which is what you want for a working file in your starting checkout. Without it, a cat from a subdirectory fails, and the && then swallows the git log too, so the hook fires and delivers nothing. One caveat if you use worktrees: that variable stays at the original root rather than following Claude into a worktree entered later, and the hook's input JSON carries the current directory as cwd for a hook that needs to know which directory Claude is working in.
Use SessionStart for this rather than the neighbouring compaction hooks, which look like a better fit and are not. PreCompact and PostCompact both fire around the event and both take a matcher distinguishing a manual compaction from an auto one, but the hooks reference names only UserPromptSubmit, UserPromptExpansion, SessionStart, and PostModelSwitch as the events where "Claude Code adds plain-text stdout as context that Claude can see and act on." For every other event, stdout goes to the debug log, so a PostCompact hook that cats your working file writes it somewhere the model never sees. PreCompact is still worth knowing because it runs before the summary and can block compaction outright, and PostCompact is documented for side effects such as logging or cleanup.
A hook restores what you told it to restore. It cannot supply a rejection reason nobody ever wrote down, and it will faithfully re-inject a working file that went stale an hour ago.
Write task state where the reload can reach it
The reload list is the clue. Up to five files come back, most recently modified first, and a file over 5,000 tokens "comes back as a path reference without its content."
So a single small file that the agent has just edited is close to the ideal shape for post-compaction state. It is recently modified, so it is near the front of the queue. It is short, so it returns with its contents rather than as a bare path.
Keep it in the repo, name it something like WORKING.md, and have the agent rewrite it as it goes rather than summarizing at the end. A file updated while a decision is still in view can record why the decision was made. An end-of-session summary can only record what the agent still remembers by then.
What belongs in it is the state of this task: the current step and the files involved, test results, open questions, and each rejected approach with its reason. If you ruled out a cache because it can serve stale reads, that sentence is the whole point of the file. Without it the agent proposes the cache again and you re-run the argument.
What does not belong in it is anything stable. Build commands, directory boundaries, and review requirements go in CLAUDE.md, which is re-injected from disk anyway. A working file that accumulates a diary gets longer every week, and a long file comes back as a path reference, which is the one outcome you were trying to avoid.
Path-scoped rules are a related trap worth knowing about. Rules with paths: frontmatter and nested CLAUDE.md files "load into message history when Claude reads, writes, or edits their trigger file, so compaction summarizes them away with everything else." If a rule has to survive, the documentation says to drop the paths: frontmatter or move it to the project-root file.
When compaction thrashes, stop feeding it
There is a failure mode where compaction runs and the context immediately refills, over and over.
Claude Code detects it. If you see Autocompact is thrashing: the context refilled to the limit..., the troubleshooting page explains that compaction succeeded but "a file or tool output immediately refilled the context window several times in a row," and Claude Code stops retrying rather than burning API calls on a loop. Its recovery steps, in order: read the oversized file in smaller chunks such as a line range, run /compact with a focus that drops the large output, move the large-file work to a subagent so it runs in a separate context window, or run /clear if the earlier conversation is no longer needed.
If the error returns after a /clear, the documentation says to run /context and compare the Messages row against the rows above it. When Messages is the largest row, something in the new conversation is refilling the window. When the other rows together are larger, what loads at session start has left too little room to work in.
What none of this fixes
Compaction still happens. A later threshold postpones it, and a long enough task reaches it anyway.
A guided summary is still a summary. Naming what to keep raises the odds that it is kept and does not guarantee it, so check the agent's account of the task against your working file before letting it make a consequential edit.
Written state only helps if something reads it. A hook re-injects what you configured, an instruction file is re-injected automatically, and a working file nobody opens is just a file. The weakest link is usually not the writing, it is the reading.
And the controls above are per developer and per session. They do nothing about a decision made in a pull request review three weeks ago that nobody wrote into your repo. That one is not a compaction problem, and no compaction setting reaches it.
That last gap is where Dosu fits. Dosu builds knowledge from the Sources a team connects, including GitHub, plus Slack and Notion on the Pro and Enterprise plans, and coding agents reach it on demand through the Dosu MCP Server. An agent that can query for a decision after its history was summarized away is not relying on the summary to have kept it. Dosu does not prevent compaction and cannot retrieve a reason nobody recorded.
A checklist for your next long session
- Add a
Compact Instructionssection to your project-rootCLAUDE.mdnaming the goal, the next step, the decisions that must hold, and the rejected approaches with reasons. - Create a short
WORKING.mdand tell the agent to update it after each decision, each completed step, and each test run. - Add a
SessionStarthook with acompactmatcher that cats that file back in. - Run
/compactwith a focus at task boundaries instead of waiting for the automatic pass, and/clearwhen switching to unrelated work. - Move stable rules out of path-scoped files into the project-root
CLAUDE.mdif they have to survive compaction. - Raise the threshold with
/autocompactonly after the above, because it buys time rather than fidelity.
FAQ
What does auto-compact do in Claude Code?
It clears older tool outputs first, then summarizes the conversation if needed, which lets a long session continue past the context limit. Afterwards it re-injects the project-root CLAUDE.md, unscoped rules, auto memory, and the plan file from disk, and re-reads up to five recently modified files. Your requests and key code snippets are preserved, and detailed instructions from early in the conversation may be lost.
How do I tell compaction apart from a context-window overflow?
Compaction keeps the session going, so the tell is behavioral rather than an error message. The agent continues working but reopens a settled question, re-reads a file it already read, or drops a constraint you set early. An overflow interrupts the request instead. Run /context to see current usage and which memory files loaded.
Should I turn off auto-compact?
Disabling it does not remove the model's context limit, it removes the thing that lets a session survive reaching it. Raising the threshold with /autocompact is the more useful version of the same instinct, and it buys a longer run rather than a better summary. Spend the effort on what compaction reloads instead.
Does a 1 million token context window solve this?
It moves the boundary. Claude Code's documentation states that compaction works the same way at the larger limit, so a bigger window means later compaction and not a different summary. Models running with a native 1M window compact at about 967K tokens by default.
Set up the file your next compaction will read back
Open your current project and create WORKING.md with four headings: current step, decisions and why, rejected approaches and why, open questions. Add a line to your project-root CLAUDE.md telling the agent to update it after each decision and to read it after compaction. The next time a session compacts, check whether the agent's first move reflects the file. That one test tells you whether your written state is reaching the model.
Then look at the decisions your agents keep re-deriving that live outside your repo, in pull request threads and chat, where no file in your working tree can reach them. Connect a repository to Dosu so an agent can query for them on demand instead of hoping a summary kept them.