Claude Code はコンテキストが埋まると会話を自動で圧縮するが、圧縮するとコンテキスト(いままでの経緯)が希薄になる。この記事では PreCompact フックで自動圧縮をブロックし、代わりに「経緯を保存して新しいチャットで再開して」と促す仕組みを紹介。
要件
やりたいことは次の通り。
- コンテキストが溢れて自動圧縮が走る直前を捕まえる
- 自動圧縮を止める(圧縮による情報の希薄化を避けたい)
- 会話の経緯を知識ベース(今回は dejavu )に残し、新しいチャットへ引き継ぐ
- どのプロジェクトでも効くように、ユーザースコープで設定する
- 追加の API 料金はかけたくない。
調査
Claude Code には圧縮の直前に発火する PreCompact フックがある。公式ドキュメントで挙動を確認したところ、次のことが分かった。
- 圧縮をブロックできる。
exit 2(stderr にメッセージ)または JSON の{"decision":"block"}を返す。 - 手動
/compactでも自動圧縮でも発火する。compact_reasonフィールドで"manual"/"auto"を判別できる。 - フックには
transcript_path(会話履歴の JSONL パス)、cwd、session_idなどが stdin の JSON で渡る。 -
exit 2の stderr はユーザーの画面に表示される。
ここで一番効いた事実がひとつ。
PreCompact のブロック理由(reason / stderr)は Claude 本体には渡らない。
PreCompact は Claude の思考ループの外、システム処理中に発火するため、フックが出したメッセージを Claude が読んで次の行動に移す、という連携はできない。つまり「フックがブロック → その指示を Claude が受けて自動保存」は成立しない。
もうひとつ大事な前提。フックの実体はシェルスクリプトであって Claude 本体ではない。だから会話を賢く要約して保存する処理を、フック単体では作れない。
手法の検討
保存を誰にやらせるかで案が割れた。
案A:フック内からヘッドレスの claude -p を呼んで要約・保存する。品質は高いが、圧縮のたびに API を1回叩くので料金がかかる。今回の制約に反する。
案B:フックが会話履歴の JSONL をそのまま知識ベースに突っ込む。無料だが要約されないので人が読めない。
案C:フックは圧縮を止めてメッセージを出すだけ。保存は、そのメッセージを見たユーザーが通常のチャットで Claude に指示して実行する。
採用したのは案C。理由は次の通り。
- フックは「止める」と「表示する」しかやらないので無料(APIを使わない)
- 実際の保存は、いつも使っている Claude が会話の文脈をそのまま持った状態で要約するので品質が高い
- その保存は通常利用サブスクの範囲で、追加料金は発生しない
案Cであれば、案Aの弱点(料金)と案Bの弱点(低品質)が両方消える。
処理の流れはこうなる。
自動圧縮が走ろうとする
→ PreCompact フック発火
→ compact_reason が "auto" なら exit 2 でブロック
→ stderr のメッセージを画面表示
→ ユーザーがメッセージ通りに指示 → 通常の Claude が知識ベースへ保存
→ 新しいチャットで復元コマンドを打って再開
実装
スクリプト
~/.claude/hooks/precompact-guard.sh を作る。stdin の JSON から compact_reason を読み、auto のときだけ exit 2 する。手動 /compact は意図的な操作なので通す。
#!/bin/bash
# PreCompact guard (user scope / 全プロジェクト共通)
input=$(cat)
reason=$(printf '%s' "$input" | /usr/bin/python3 -c '
import sys, json
try:
d = json.load(sys.stdin)
except Exception:
d = {}
print(d.get("compact_reason") or d.get("trigger") or "")
' 2>/dev/null)
if [ "$reason" = "auto" ]; then
echo 'コンテキストが溢れそうです。"dejavuに経緯を保存" と伝えて、新しいチャットで"dejavu さっきの続き" で再開してください' >&2
exit 2
fi
exit 0
compact_reason が正式なフィールド名だが、版差を吸収するために trigger もフォールバックで見ている。
設定への登録
~/.claude/settings.json の hooks に PreCompact を追加する。ユーザースコープなので全プロジェクトで効く。
{
"hooks": {
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash \"$HOME/.claude/hooks/precompact-guard.sh\"",
"timeout": 5
}
]
}
]
}
}
動作確認
フック本番は実際の自動圧縮でしか出ないので、スクリプト単体を先に動作確認する。
echo '{"compact_reason":"auto"}' | bash ~/.claude/hooks/precompact-guard.sh; echo "exit=$?"
echo '{"compact_reason":"manual"}' | bash ~/.claude/hooks/precompact-guard.sh; echo "exit=$?"
hookを登録した後は Claude Code を起動し直すと反映される。
まとめ
PreCompact フックは圧縮をブロックできるが、その理由を Claude 本体に渡すことができない。これを踏まえてフックは「止めて促す」だけに徹し、保存の本体は普段の Claude に任せる。これで自動圧縮による希薄化を防ぎつつ、要約の品質と料金ゼロを両立。
「hookで自動圧縮→次チャット自動開設→引き継ぎも自動」ができれば快適だが、それなりにコストが掛かる。今回紹介した方法のようにClaudeが自動的にコンテキストを希薄にしてしまう挙動を抑えるだけでも価値はあると思う。