💾

Claude Code の PreCompact hook でコンテキスト消失を防ぐ

に公開

コンパクション前のコンテキストが消える問題

Claude Code で長いセッションを続けていると、コンテキストが上限に達して自動的に圧縮される。この圧縮(compaction)は便利な機能だが、圧縮前のコンテキストが消えてしまう。

セッション再開時に「さっき何話してたっけ?」となる。特に複数の話題が並行していた場合、圧縮で消えた情報を思い出すのは難しい。

そこで PreCompact hook を実装した。コンパクション実行前に自動的にコンテキストをバックアップする仕組みだ。

PreCompact hook とは

Claude Code には hook 機能があり、特定のタイミングでスクリプトを実行できる。PreCompact hook は /compact 実行時、圧縮処理のに発火する。

通常のバックアップとの違いは以下の通り:

タイミング hook名 用途
圧縮前 PreCompact コンテキスト退避(自動)
圧縮後 PostCompact ログ記録・通知(必要に応じて)
セッション開始時 OnSessionStart 初期化処理

私が欲しかったのは圧縮前のバックアップ。圧縮後だとすでに情報が失われている。

実装の流れ

バックアップスクリプトの作成

まず、コンテキストを保存するスクリプトを用意した。

~/.claude/hooks/pre-compact-save-context.sh:

#!/bin/bash

# 日本時間の日付パスを生成
DATE_PATH=$(TZ=Asia/Tokyo date +%Y/%m/%d)
BACKUP_DIR=~/ai-tasklogs/sessions/$DATE_PATH/context-backups

# バックアップディレクトリ作成
mkdir -p "$BACKUP_DIR"

# コンテキストファイルをコピー
# SESSION_ID と CONTEXT_FILE は環境変数として渡される
TIMESTAMP=$(TZ=Asia/Tokyo date +%Y%m%d-%H%M%S)
cp "$CONTEXT_FILE" "$BACKUP_DIR/${SESSION_ID}_${TIMESTAMP}_pre-compact.jsonl"

このスクリプトは以下を行う:

  1. 日本時間で日付ディレクトリを作成 (2025/12/30/)
  2. セッションIDとタイムスタンプでファイル名を生成
  3. コンテキストファイル(.jsonl)をコピー

settings.json への設定

次に ~/.claude/settings.json で hook を登録する。

最初はこう書いた:

"PreCompact": [
  {
    "matcher": "auto|manual",
    "hooks": [
      {
        "type": "command",
        "command": "/Users/nomuraya/.claude/hooks/pre-compact-save-context.sh"
      }
    ]
  }
]

auto|manual でパイプ区切りすれば、自動と手動の両方で発火すると思っていた。

ハマったポイント: matcher の罠

テストで /compact を実行したが、hook が発火しない。

バックアップディレクトリを確認:

$ ls -lht ~/ai-tasklogs/sessions/2025/12/30/context-backups/
-rw------- 1 nomuraya staff 6.6M Dec 30 12:44 xxx_manual-backup.jsonl

これは手動で作った別のバックアップ。_pre-compact.jsonl が作られていない。

スクリプト自体を手動実行すると動く。つまり、hook が呼ばれていない。

原因は matcher の書き方だった。Claude Code の matcher はパイプ区切りに対応していない。正しくはこう:

"PreCompact": [
  {
    "matcher": "manual",
    "hooks": [
      {
        "type": "command",
        "command": "/Users/nomuraya/.claude/hooks/pre-compact-save-context.sh"
      }
    ]
  },
  {
    "matcher": "auto",
    "hooks": [
      {
        "type": "command",
        "command": "/Users/nomuraya/.claude/hooks/pre-compact-save-context.sh"
      }
    ]
  }
]

automanual で別々のエントリーにする。

もう一つの落とし穴: 拡張のリロード

設定を修正して再度 /compact を実行。

結果は変わらず。hook が発火しない。

ここで気づいた。VSCode 拡張は設定ファイルの変更を即座に反映しない。

対処法:

  1. VSCode のコマンドパレット (Cmd+Shift+P)
  2. "Developer: Reload Window" を実行

または Claude Code を一度終了して再起動。

リロード後にもう一度 /compact を実行すると:

PreCompact [/Users/nomuraya/.claude/hooks/pre-compact-save-context.sh] completed successfully

成功メッセージが表示された。

動作確認

バックアップディレクトリを確認:

$ ls -lht ~/ai-tasklogs/sessions/2025/12/30/context-backups/ | head -5
-rw------- 1 nomuraya staff 7.0M Dec 30 13:04 xxx_pre-compact.jsonl
-rw------- 1 nomuraya staff 6.6M Dec 30 12:44 xxx_manual-backup.jsonl

13:04 に _pre-compact.jsonl が作成されている。ファイルサイズは 7.0M。

正常にバックアップが取得できた。

タイムスタンプも正しい:

  • 12:44 → 手動バックアップ(旧)
  • 13:04 → PreCompact hook によるバックアップ(新)

運用での気づき

PreCompact hook を実装して気づいたこと:

VSCode 拡張のリロードが必須

settings.json を変更した後、VSCode 拡張のリロードが必要。これは盲点だった。

他の設定変更でも同様の可能性がある。設定を変えたら念のためリロードする習慣をつけた方がよさそうだ。

matcher の仕様

パイプ区切りが使えないのは少し意外だった。同じスクリプトを複数 matcher で使う場合、エントリーを複製する必要がある。

DRY 原則には反するが、YAML や JSON の制約上仕方ない。

バックアップファイルの命名規則

ファイル名に _pre-compact.jsonl というサフィックスをつけることで、手動バックアップと区別している。

後から見返したときに、どのタイミングで取得したバックアップかが一目でわかる。

今後の運用

この仕組みで、コンパクション前のコンテキストが自動的に保存される。

セッション再開時に「あれ、何話してたっけ?」となっても、バックアップファイルを確認すれば復元できる。

設定変更後のリロード忘れには注意が必要だが、一度動けば安定して動作する。長期セッションでのコンテキスト管理が楽になった。

Discussion