Claude Code PreCompact hooks run before context compaction and can block compaction, which makes them useful but dangerous for long-running agent tasks.
That is the practical sentence.
Not the exciting sentence.
The exciting sentence is usually something like this:
I added a hook so the agent never loses state during a long task.
That sounds great.
It also sounds like the beginning of a debugging afternoon.
PreCompact is not a magic memory system.
It is a lifecycle hook.
It fires before compaction.
It can inspect the event input.
It can log state.
It can block compaction.
But if you block the wrong compaction at the wrong time, the current request may fail instead of continuing with a cleaner context.
So the question is not whether PreCompact hooks are powerful.
They are.
The better question is when they should block, when they should only record state, and when they should stay out of the way.
This article is written for teams running Claude Code on real tasks: migrations, refactors, test-repair loops, multi-file edits, subagent handoffs, release prep, and other jobs where โjust compact and continueโ can lose the one detail that mattered.
It is not a claim that every Claude Code project needs a PreCompact hook.
Most do not.
But if your sessions keep losing task state right before the expensive part of the work, this hook is worth understanding.
The Fast Rule
Use a PreCompact hook to protect an active task boundary, not to preserve every token.
That distinction matters.
A useful PreCompact hook answers one of these questions:
- Is there an in-flight edit that has not been summarized?
- Is there a running test, migration, or publish job that must finish first?
- Is there a task ledger or handoff file that needs to be updated before compact?
- Is this a manual
/compactwhere the user gave instructions that should be archived? - Is this an automatic compact that should be allowed because the task is between phases?
If the hook cannot answer a concrete operational question, it probably should not block.
Logging is cheap.
Blocking is a policy decision.
Treat it like one.
What PreCompact Actually Does
Claude Codeโs official hooks reference describes PreCompact as running before Claude Code is about to run a compact operation.
The matcher tells you what triggered it.
manual means the user invoked /compact.
auto means auto-compaction was triggered when the context window became full.
The official docs also state that PreCompact hooks can block compaction.
They can do that by exiting with code 2 or by returning JSON with "decision": "block".
That is the sharp edge.
The same documentation notes that blocking automatic compaction behaves differently depending on timing.
If the automatic compact was proactive, Claude Code can skip compact and continue uncompacted.
If the compact was triggered after a context-limit error already came back from the API, the underlying error surfaces and the current request fails.
This is the part teams should read twice.
Blocking compact is not always a graceful pause.
Sometimes it is a controlled refusal.
Sometimes it is the reason the turn fails.
That does not make PreCompact bad.
It means the hook needs a narrow trigger.
The Input You Get
PreCompact receives common hook fields plus two fields that matter here:
triggercustom_instructions
For a manual compact, custom_instructions contains what the user passed to /compact.
For an automatic compact, it is empty.
That gives you a simple split:
- Manual compact can be treated like a user-driven checkpoint.
- Automatic compact should be treated like a context-pressure event.
Those are not the same workflow.
A manual /compact may be a good moment to archive a task state file and let compaction proceed.
An automatic compact during a long-running refactor may be a warning that the session is about to cross a fragile boundary.
The hook should not use the same policy for both unless the task is intentionally simple.
When Blocking Makes Sense
Blocking PreCompact can make sense when compaction would erase operational state that has not been persisted anywhere else.
For example, imagine a long-running agent task is halfway through a migration.
It has already changed database models.
It has not yet updated the API layer.
It has a failing test list in the conversation.
It has not written a task ledger to disk.
Auto-compact fires.
If the summary misses one failure mode, the next phase can wander.
That is a reasonable case for a hook to block or at least force a state checkpoint first.
The same is true for multi-agent workflows.
If subagents are running, and the main session has not recorded which worker owns which file set, compaction can turn a clean ownership model into mush.
Mush is not a workflow.
Mush is soup wearing a lanyard.
Write the state down first.
Then compact.
When Blocking Is A Bad Idea
Blocking PreCompact is a bad idea when the task state is already externalized.
If the plan is in a file, the checklist is updated, tests are committed to logs, and pending work is visible in a durable place, compacting is usually fine.
The whole point of compaction is to keep the session moving.
If the hook blocks every automatic compact because โcontext loss is scary,โ it becomes a different problem.
Now the session cannot compact, the context keeps growing, and the next request may fail at the context limit.
That is not reliability.
That is hoarding with JSON.
The hook should not defend the transcript.
It should defend the task.
If the task is safe, let compact proceed.
A Practical Decision Table
| Situation | Block PreCompact? | Better action |
|---|---|---|
Manual /compact after a clean phase boundary |
No | Log checkpoint, allow compact |
| Auto compact while tests are running externally | Maybe | Record test command/session id first |
| Auto compact during a multi-file edit with no ledger | Yes, briefly | Ask user or write task state before continuing |
| Auto compact after a context-limit error | Usually no | Blocking may fail the request |
| Subagents running with unclear ownership | Maybe | Save agent ownership map |
| All task state is already in files | No | Allow compact |
| Hook cannot inspect useful state | No | Log only |
The table is intentionally conservative.
A hook that blocks too rarely is just a logger.
That is fine.
A hook that blocks too often becomes a workflow tax.
That is worse.
The Minimal Pattern
A reasonable first version does not try to be clever.
It reads the PreCompact input.
It writes a compact checkpoint.
It blocks only if a known lock file or task marker says the current phase is unsafe to compact.
Something like this:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
TRIGGER="$(printf '%s' "$INPUT" | jq -r '.trigger // "unknown"')"
CWD="$(printf '%s' "$INPUT" | jq -r '.cwd')"
mkdir -p "$CWD/.claude/compact-checkpoints"
printf '%s\n' "$INPUT" > "$CWD/.claude/compact-checkpoints/last-precompact.json"
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$CWD/.claude/compact-checkpoints/last-precompact-at.txt"
if [ -f "$CWD/.claude/compact-lock" ] && [ "$TRIGGER" = "auto" ]; then
echo "Auto-compact blocked: .claude/compact-lock exists. Write a task checkpoint, remove the lock, then compact." >&2
exit 2
fi
exit 0
This is boring on purpose.
It does not ask an LLM to judge the task.
It does not parse the whole transcript.
It does not invent a new memory layer.
It creates evidence, checks one explicit lock, and gets out.
You can make it smarter later.
Start with boring.
Boring is easier to trust at 1:12 a.m.
A Settings Example
Claude Code hooks are configured in settings files.
Project-level hooks can live in .claude/settings.json.
Local-only hooks can live in .claude/settings.local.json.
For a project hook, a minimal configuration can look like this:
{
"hooks": {
"PreCompact": [
{
"matcher": "auto",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/precompact-checkpoint.sh"
}
]
}
]
}
}
The matcher matters.
If you only care about automatic compaction, match auto.
If you only want manual compact checkpoints, match manual.
If you want both, configure both intentionally.
Do not start with a catch-all hook unless you know why.
Lifecycle hooks are not decoration.
They are automation.
Automation deserves smaller blast radii.
What To Save Before Compact
The hook does not need to save everything.
It needs to save what the compact summary may flatten.
For long-running agent work, useful checkpoint fields include:
- Current task name.
- Current phase.
- Files already changed.
- Files intentionally not touched.
- Commands currently running.
- Test failures still unresolved.
- Subagent ownership.
- User constraints that must survive compact.
- Public URLs, deployment IDs, or external job IDs.
- Next command to run after compact.
That list should live outside the conversation.
A markdown file works.
A JSON file works.
A task board works.
The format matters less than the fact that it is durable and short.
If the checkpoint becomes another huge transcript, you moved the problem sideways.
What Not To Save
Do not dump secrets.
Do not dump full environment variables.
Do not dump full command outputs unless they are essential.
Do not copy entire source files into the checkpoint.
Do not save tokens just because they exist.
A compact checkpoint is not a museum.
It is a handoff note.
The best checkpoint is small enough that the next model turn can read it without becoming the new context problem.
This is the same reason teams should not stuff every project rule into every prompt.
Context is a budget.
Spend it like money you actually had to earn.
PreCompact vs PostCompact
PreCompact and PostCompact solve different problems.
PreCompact is the guard.
PostCompact is the cleanup observer.
The official hooks reference describes PostCompact as running after compaction completes and receiving the generated compact summary.
PostCompact cannot affect the compaction result.
That makes it good for logging the new summary, updating external state, or validating that a compact happened.
It is not the right place to block a risky compact.
By the time PostCompact runs, the compact already happened.
If the question is โshould we allow this compact right now,โ use PreCompact.
If the question is โwhat happened after compact,โ use PostCompact.
Mixing those roles is how small automations get weird.
PreCompact vs Stop Hooks
Stop hooks are also useful for long-running agent work.
But they fire when Claude finishes responding.
That makes them good for completion gates:
- Did tests pass?
- Did the draft publish?
- Did the checklist finish?
- Did the agent stop too early?
PreCompact is different.
It fires before context compression.
It is a continuity gate, not a completion gate.
Use Stop hooks to prevent premature โdone.โ
Use PreCompact hooks to prevent unsafe context compression.
Use both only when the workflow truly needs both.
Otherwise the team will spend more time debugging hooks than shipping work.
That is the least romantic kind of automation.
Where Teams Usually Overdo It
Teams overdo hooks in three ways.
First, they turn every hook into an LLM judge.
That can be useful for ambiguous policy checks, but PreCompact usually starts better as a deterministic script.
The signal is simple:
- Is there a lock?
- Is there a task state file?
- Is a known external job running?
- Is the trigger manual or auto?
You do not need a model to decide every one of those.
Second, they block too much.
If every auto-compact gets blocked, the team has not built memory.
It has built a context-limit trap.
Third, they save too much.
Dumping the transcript into another file can help with audit trails, but it does not automatically help the next agent step.
The next step needs a short handoff.
Not the whole opera.
A Safer Operating Rule
Here is the rule I would use on a team:
Allow compaction by default.
Block only when a durable task checkpoint is missing and the current phase is marked unsafe.
That gives you three control points:
- A task can be marked unsafe.
- A checkpoint can clear the unsafe state.
- Auto-compact can proceed when no explicit unsafe state exists.
This prevents the hook from becoming superstitious.
It also gives the developer an escape hatch.
If the task is truly safe, remove the lock and compact.
If the task is not safe, write the checkpoint first.
Simple rules survive tired humans.
Complicated hook theology does not.
Example Workflow For A Refactor
Before starting a long refactor, create a task state file:
phase: api-layer-migration
unsafe_to_compact: true
changed:
- src/db/models.ts
- src/api/users.ts
pending:
- update service tests
- run npm test -- users
- check migration rollback path
next:
- finish src/services/user-service.ts
Then create a compact lock:
touch .claude/compact-lock
During the refactor, the PreCompact hook blocks auto-compaction.
Before allowing compact, update the task state:
phase: test-repair
unsafe_to_compact: false
next: run npm test -- users
Then remove the lock:
rm .claude/compact-lock
Now compact can happen without relying on the transcript to preserve the task.
That is the whole point.
The hook is not the memory.
The task file is the memory.
The hook protects the moment before that memory exists.
When Subagents Are Involved
PreCompact gets more important when subagents are involved, but it also gets easier to misuse.
If three workers are editing different file sets, the main session needs a short ownership map.
For example:
# Agent Ownership
worker-a:
owns: frontend/auth/*
status: editing
worker-b:
owns: backend/session/*
status: tests running
main:
owns: integration and final review
If that map only exists in conversation, compact can blur the boundary.
That increases the chance of duplicate work or accidental overwrite.
A PreCompact hook can block until the ownership map exists.
But once the map exists, compact should usually be allowed.
Again, the goal is not to prevent compaction forever.
The goal is to make compaction less destructive.
Security Notes
Hooks run commands.
That alone should make you cautious.
Keep hook scripts small.
Keep them in the project when they are part of the project workflow.
Use local settings for personal experiments.
Avoid writing secrets into checkpoint files.
Avoid blindly executing data from hook input.
Quote paths.
Use jq carefully.
Do not make PreCompact call arbitrary network services unless the team has a reason.
The official Claude Code documentation includes security considerations for hooks for a reason.
Lifecycle automation can be useful.
It can also become a hidden policy engine that nobody reviews.
If a hook can block work, it deserves code review.
A Good First Checklist
Before adding a PreCompact hook, answer these:
- What task state is currently lost during compact?
- Can that state be written to a normal file instead?
- Should manual and automatic compact behave differently?
- What exact condition should block compact?
- How does a developer unblock it?
- What happens if the context is already at the limit?
- Does the hook ever write secrets?
- Is the hook short enough to review?
- Does it fail open or fail closed?
- Who owns the hook when it breaks?
If those questions feel annoying, that is good.
They are cheaper than debugging a hidden lifecycle script later.
The Practical Recommendation
For most teams, the best PreCompact setup is not a smart blocker.
It is a checkpoint logger plus one explicit lock.
Log the event.
Save the trigger.
Save the working directory.
Record the current task state if a file exists.
Block only when .claude/compact-lock exists during automatic compaction.
That gives you a reliable safety valve without turning every compact into a ceremony.
If the workflow matures, you can add richer checks:
- running test sessions
- active subagent ownership
- deployment state
- migration phase markers
- release checklist completion
But do not start there.
Start with one lock and one handoff file.
That is enough to catch the common failure mode.
FAQ
Can a PreCompact hook block compaction?
Yes.
The current Claude Code hooks reference says PreCompact can block compaction with exit code 2 or JSON output containing "decision": "block".
Should I block every automatic compact?
No.
Blocking every automatic compact can push the session toward context-limit failure.
Block only when a known unsafe task phase has not been checkpointed.
Is PreCompact a memory system?
No.
It is a lifecycle hook.
The durable memory should be a task file, issue, checkpoint, or external system.
PreCompact can protect the moment before that state is safely written.
Should the hook use an LLM?
Usually not at first.
For PreCompact, deterministic checks are often enough: trigger type, lock file, task state file, running job marker.
Use an LLM only if the decision truly requires semantic judgment.
What is the difference between PreCompact and PostCompact?
PreCompact runs before compaction and can block it.
PostCompact runs after compaction and is better for logging or reacting to the generated compact summary.
What is the safest first implementation?
Use a script that logs PreCompact input and blocks only when an explicit project lock file exists.
That keeps the policy visible, reviewable, and easy to override.
Related Posts
- Claude Code /usage in 2026: how to find whether subagents, parallel sessions, or long context are burning limits
- Codex plugins and remote marketplaces in 2026: what to check before installing agent tooling from GitHub
- Claude Code PreCompact hook์ ๊ธด ์์ ์ค๋จ์ ์ด๋๊น์ง ๋ง์์ค๊น 2026
Official References
Sources
Closing Thought
PreCompact hooks are most useful when they are boring.
That sounds like an insult.
It is not.
The best hook in a long-running agent workflow is the one that quietly preserves the task boundary and then disappears.
If it blocks, the reason should be obvious.
If it allows compact, the task state should already be durable.
That is the balance.
Do not use PreCompact to fight compaction.
Use it to make compaction survivable.