セッションストレージ
UUID親子チェーンを持つJSONLトランスクリプトについて学ぶ。
学ぶこと
Claude Codeとのすべての会話は記録されます。曖昧な記憶としてではなく、JSONL形式の正確な1行ごとのトランスクリプトとしてです。これにより、再起動をまたいでセッションが永続化され、数日後に作業を再開でき、システムが継続性を維持します。
このセッションを終えると、以下を理解できます:
- Claude Codeが会話状態をディスクにどのように永続化するか
- JSONL(JSON Lines)形式とその選択理由
- セッションIDとUUID親子チェーン
- 各トランスクリプトエントリに格納されるデータ
claude --resumeによるセッション再開の仕組み- 古いセッションが時間とともにクリーンアップされる方法
課題
作業中にターミナルを閉じると、メモリ内のすべてが消えます。コンテキストウィンドウ、計画、作業中のファイル — すべて失われます。
永続化がなければ、すべてのセッションはゼロからのスタートです:
月曜日: "認証モジュールをリファクタリング" → 45分の作業 → ターミナルを閉じる
火曜日: "認証モジュールをリファクタリング" → ゼロからやり直し
本格的な開発作業では、これは許容できません。AIが中断したところから再開し、何が行われ、何が失敗し、何が残っているかを理解する必要があります。
解決策:すべてのメッセージ交換を、リプレイ可能な形式でディスクに書き込みます。
仕組み
JSONL形式
Claude CodeはトランスクリプトをJSONL — JSON Lines — ファイルとして保存します。各行は、会話内の1つのイベントを表す自己完結型のJSONオブジェクトです:
{"type":"message","role":"user","content":"認証バグを修正して","timestamp":"2026-03-20T10:00:01Z","sessionId":"a1b2c3d4-..."}
{"type":"message","role":"assistant","content":[{"type":"text","text":"認証モジュールを見てみましょう..."},{"type":"tool_use","id":"tu_1","name":"Read","input":{"file_path":"src/auth.ts"}}],"timestamp":"2026-03-20T10:00:03Z","sessionId":"a1b2c3d4-..."}
{"type":"tool_result","tool_use_id":"tu_1","content":"export function validateToken(token: string) { ... }","timestamp":"2026-03-20T10:00:03Z","sessionId":"a1b2c3d4-..."}
なぜ単一のJSON配列ではなくJSONLなのか?
| 形式 | 追加コスト | パースコスト | 破損耐性 |
|---|---|---|---|
JSON配列 [...] | ファイル全体を書き直し | ファイル全体をパース | 1バイトの不正で全体が破損 |
| JSONL(1行ずつ) | 1行追加 | 1行ずつパース | 不正な行はスキップ可能 |
JSONLは追記専用です。つまり、新しいメッセージはファイルの末尾に書き込まれるだけです。書き直し、ロック、既存データの破損リスクはありません。
セッションID:UUID識別
すべてのセッションは開始時にUUID(Universally Unique Identifier)を取得します:
Session: a1b2c3d4-e5f6-7890-abcd-ef1234567890
このUUIDはトランスクリプトのすべての行に表示され、すべてのイベントをそのセッションにリンクします。ファイル名にも表示されます:
~/.claude/projects/<project-hash>/
sessions/
a1b2c3d4-e5f6-7890-abcd-ef1234567890.jsonl
b2c3d4e5-f6a7-8901-bcde-f12345678901.jsonl
c3d4e5f6-a7b8-9012-cdef-123456789012.jsonl
親子セッションチェーン
セッションは孤立していません — チェーンを形成します。セッションを再開したりサブエージェントを生成すると、親子関係が作成されます:
┌──────────────────────────────────────────────┐
│ Session Chain │
│ │
│ ┌─────────────────────┐ │
│ │ Session A (original) │ │
│ │ ID: a1b2c3d4-... │ │
│ │ parent: null │ │
│ └─────────┬────────────┘ │
│ │ │
│ ┌───────┴────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Session B │ │ Session C │ │
│ │ (resumed) │ │ (subagent)│ │
│ │ parent: │ │ parent: │ │
│ │ a1b2c3d4 │ │ a1b2c3d4 │ │
│ └──────────┘ └─────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ Session D │ │
│ │ (sub-sub) │ │
│ │ parent: │ │
│ │ c3d4e5f6 │ │
│ └──────────┘ │
│ │
└──────────────────────────────────────────────┘
各セッションエントリは親セッションIDを保存し、ツリーを形成します:
{
"sessionId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"parentSessionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "session_start",
"timestamp": "2026-03-20T14:30:00Z",
"resumedFrom": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
このチェーンにより、システムは以下が可能になります:
- 複数の作業セッションにわたるタスクの完全な履歴を追跡
- どのサブエージェントセッションがどの親に属するかを把握
- 再開時に完全なコンテキストを再構築
格納されるデータ
JSONLトランスクリプトの各行は、特定のイベントタイプをキャプチャします:
| イベントタイプ | 内容 | 目的 |
|---|---|---|
session_start | セッションID、親ID、タイムスタンプ、プロジェクトパス | セッションの開始をマーク |
message | ロール、コンテンツブロック、タイムスタンプ | ユーザーまたはアシスタントのメッセージ |
tool_use | ツール名、入力パラメータ、tool_use_id | AIが実行したいこと |
tool_result | tool_use_id、出力内容、所要時間 | 実際に起こったこと |
compaction | 要約テキスト、節約トークン数 | コンテキストが圧縮された |
session_end | 所要時間、トークン数、コスト | セッション統計 |
ツールコールの典型的なトランスクリプトエントリは次のようになります:
{
"type": "tool_use",
"sessionId": "a1b2c3d4-...",
"toolName": "Bash",
"toolInput": {
"command": "npm test",
"description": "Run test suite"
},
"toolUseId": "tu_abc123",
"timestamp": "2026-03-20T10:05:22Z"
}
その結果が続きます:
{
"type": "tool_result",
"sessionId": "a1b2c3d4-...",
"toolUseId": "tu_abc123",
"content": "Tests: 14 passed, 2 failed",
"durationMs": 3400,
"timestamp": "2026-03-20T10:05:25Z"
}
セッション再開
claude --resumeを実行すると、システムは以下を行います:
- 現在のプロジェクトの最近のセッションを一覧表示
- 選択されたセッションのJSONLトランスクリプトを読み取り
- トランスクリプトイベントからメッセージ配列を再構築
- 復元されたコンテキストが大きすぎる場合は圧縮を適用
parentSessionIdで元のセッションにリンクされた新しい子セッションを作成
claude --resume
│
▼
┌──────────────┐
│ Find recent │
│ sessions for │
│ this project │
└──────┬───────┘
│
▼
┌──────────────┐
│ Parse JSONL │
│ transcript │
│ line by line │
└──────┬───────┘
│
▼
┌──────────────┐
│ Rebuild │
│ message array │
│ (replay) │
└──────┬───────┘
│
▼
┌──────────────┐ ┌──────────────┐
│ Context too │─Yes─│ Compact to │
│ large? │ │ fit window │
└──────┬───────┘ └──────┬───────┘
│ No │
▼ ▼
┌──────────────────────────────┐
│ New session (child of │
│ original), ready to continue │
└──────────────────────────────┘
再開されたセッションは、以前に何が起こったかを完全に認識しています。どのファイルが編集され、どのコマンドが実行され、ユーザーの目標が何であったかを知っています。
トランスクリプトのクリーンアップ
セッションは時間とともに蓄積されます。Claude Codeはクリーンアップによってこれを管理します:
- 最近のセッションは高速な再開のためにそのまま保持
- 古いセッションはトランスクリプトが圧縮またはトリミングされる場合がある
- 非常に古いセッションは最終的にディスクから削除
- プロジェクトディレクトリはプロジェクトパスのハッシュでスコープされ、異なるプロジェクトのセッションを分離
ストレージの場所は、プロジェクトの絶対パスのハッシュを使用します:
~/.claude/projects/
abc123def/ ← hash of /Users/you/project-alpha
sessions/
settings.json
fed321cba/ ← hash of /Users/you/project-beta
sessions/
settings.json
重要なポイント
セッションストレージは、一時的な会話を永続的な作業ログに変えます。 JSONL形式は単なる実装の詳細ではありません。それは根本的な設計哲学を反映しています:会話は変更可能なドキュメントではなく、追記専用のイベントストリームです。
これが重要な理由:
- 任意のセッションを再開可能 — ノートPCを閉じて、翌日開き直し、中断したところから正確に続行できます
- サブエージェントの作業が追跡可能 — すべてのサブエージェントは親にリンクされた独自のセッションを作成するため、何が起こったかを監査できます
- 形式が人間に読める — JSONLファイルを開いて、手動で会話を読み通せます
- クラッシュリカバリが組み込み — 各行が独立して有効なため、書き込み途中のクラッシュでは最後の不完全な行のみが失われます
親子チェーンは特に強力です。システムが単一の会話だけでなく、作業のツリー全体を再構築できることを意味します — メインセッション、それが生成したサブエージェント、そのサブエージェントが生成したサブエージェント — すべてがUUIDでリンクされています。
ハンズオン例
セッショントランスクリプトの検査
自分のセッショントランスクリプトを直接調べることができます:
# プロジェクトのセッションディレクトリを見つける
ls ~/.claude/projects/
# 最近のセッションを一覧表示(更新時刻順)
ls -lt ~/.claude/projects/<project-hash>/sessions/ | head -10
# トランスクリプトの最初の数行を読む
head -5 ~/.claude/projects/<project-hash>/sessions/<session-id>.jsonl
# 1行をプリティプリントして構造を確認
head -1 sessions/<id>.jsonl | python3 -m json.tool
# セッション内のツールコール数をカウント
grep -c '"type":"tool_use"' sessions/<id>.jsonl
# セッション内のすべてのファイル編集を検索
grep '"toolName":"Edit"' sessions/<id>.jsonl | python3 -m json.tool
セッション要約スクリプトの構築
セッションで何が起こったかを要約するシンプルなスクリプトを紹介します:
import json
import sys
def summarize_session(filepath):
tools_used = {}
messages = 0
start_time = None
end_time = None
with open(filepath) as f:
for line in f:
entry = json.loads(line.strip())
timestamp = entry.get("timestamp")
if not start_time:
start_time = timestamp
end_time = timestamp
if entry.get("type") == "message":
messages += 1
elif entry.get("type") == "tool_use":
tool = entry.get("toolName", "unknown")
tools_used[tool] = tools_used.get(tool, 0) + 1
print(f"Session: {filepath}")
print(f"Duration: {start_time} to {end_time}")
print(f"Messages: {messages}")
print(f"Tool calls:")
for tool, count in sorted(tools_used.items(), key=lambda x: -x[1]):
print(f" {tool}: {count}")
summarize_session(sys.argv[1])
これにより、次のような出力が得られます:
Session: sessions/a1b2c3d4-....jsonl
Duration: 2026-03-20T10:00:01Z to 2026-03-20T10:45:33Z
Messages: 34
Tool calls:
Read: 12
Edit: 8
Bash: 6
Grep: 4
Glob: 3
何が変わったか
| セッションストレージなし | JSONLトランスクリプトあり |
|---|---|
| すべてのセッションがゼロからスタート | 任意の以前のセッションを再開可能 |
| 過去の作業の記録なし | すべてのアクションの完全な監査証跡 |
| サブエージェントの作業が見えない | 親子チェーンですべての作業を追跡 |
| クラッシュですべて失われる | 追記専用形式がクラッシュに耐える |
| コンテキストはメモリのみ | コンテキストはディスクから再構築 |
次のセッション
セッションがどのように保存され、リンクされるかを理解しました。セッション17では、Claude Codeプロジェクトで最もインパクトのあるファイルを扱います:CLAUDE.md設計 — プロジェクト指示を最大限の効果を発揮するように設計する方法と、AIの動作を制御する3階層の階層構造を学びます。