Session 儲存
了解使用 UUID 父子鏈的 JSONL 逐字稿。
你將學到什麼
你與 Claude Code 的每次對話都會被記錄下來。不是模糊的記憶,而是以 JSONL 格式精確的逐行逐字稿。這就是 session 如何在重啟後持續存在、你如何在幾天後恢復工作、以及系統如何維持連續性的原理。
完成後,你將了解:
- Claude Code 如何將對話狀態持久化到磁碟
- JSONL(JSON Lines)格式以及為什麼選擇它
- Session ID 和 UUID 父子鏈
- 每個逐字稿條目儲存了什麼資料
- Session 恢復如何透過
claude --resume運作 - 舊 session 如何隨時間被清理
問題是什麼
當你在任務進行中關閉終端時,記憶體中的一切都消失了。上下文視窗、計劃、你正在處理的檔案 — 全部丟失。
沒有持久化,每個 session 都從零開始:
Monday: "Refactor the auth module" → 45 min of work → close terminal
Tuesday: "Refactor the auth module" → starts over from scratch
對於嚴肅的開發工作來說,這是不可接受的。你需要 AI 從中斷處繼續,理解什麼已經完成、什麼失敗了、什麼還剩下。
解決方案:在每次訊息交換發生時就將其寫入磁碟,使用可以重播的格式。
如何運作
JSONL 格式
Claude Code 將逐字稿儲存為 JSONL — JSON Lines — 檔案。每一行是一個獨立的 JSON 物件,代表對話中的一個事件:
{"type":"message","role":"user","content":"Fix the auth bug","timestamp":"2026-03-20T10:00:01Z","sessionId":"a1b2c3d4-..."}
{"type":"message","role":"assistant","content":[{"type":"text","text":"Let me look at the auth module..."},{"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-..."}
為什麼用 JSONL 而不是單一 JSON 陣列?
| 格式 | 寫入成本 | 解析成本 | 損壞抵抗力 |
|---|---|---|---|
JSON array [...] | 重寫整個檔案 | 解析整個檔案 | 一個壞位元組損壞全部 |
| JSONL(每行一條) | 附加一行 | 逐行解析 | 壞行可以跳過 |
JSONL 是只附加的,這意味著每個新訊息只是簡單地寫入檔案末尾。不需要重寫、不需要鎖定、不會對現有資料造成損壞風險。
Session ID:UUID 識別
每個 session 在啟動時會取得一個 UUID(通用唯一識別碼):
Session: a1b2c3d4-e5f6-7890-abcd-ef1234567890
這個 UUID 出現在逐字稿的每一行中,將所有事件連結到其 session。它也出現在檔案名稱中:
~/.claude/projects/<project-hash>/
sessions/
a1b2c3d4-e5f6-7890-abcd-ef1234567890.jsonl
b2c3d4e5-f6a7-8901-bcde-f12345678901.jsonl
c3d4e5f6-a7b8-9012-cdef-123456789012.jsonl
父子 Session 鏈
Session 不是孤立的 — 它們形成鏈。當你恢復一個 session 或產生子代理時,就會建立父子關係:
┌──────────────────────────────────────────────┐
│ 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 │ │
│ └──────────┘ │
│ │
└──────────────────────────────────────────────┘
每個 session 條目儲存父 session 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"
}
這條鏈讓系統能夠:
- 追蹤一個任務跨多次工作的完整歷史
- 理解哪些子代理 session 屬於哪個父 session
- 在恢復時重建完整的上下文
儲存了什麼
JSONL 逐字稿中的每一行捕捉一個特定的事件類型:
| 事件類型 | 包含什麼 | 用途 |
|---|---|---|
session_start | Session ID、父 ID、時間戳、專案路徑 | 標記 session 開始 |
message | 角色、內容區塊、時間戳 | 使用者或助理訊息 |
tool_use | 工具名稱、輸入參數、tool_use_id | AI 想做什麼 |
tool_result | tool_use_id、輸出內容、持續時間 | 實際發生了什麼 |
compaction | 摘要文字、節省的 token 數 | 上下文被壓縮了 |
session_end | 持續時間、token 數、成本 | Session 統計資料 |
一個工具呼叫的典型逐字稿條目:
{
"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"
}
Session 恢復
當你執行 claude --resume 時,系統會:
- 列出當前專案的近期 session
- 讀取所選 session 的 JSONL 逐字稿
- 從逐字稿事件重建訊息陣列
- 如果恢復的上下文太大則進行壓縮
- 建立一個新的子 session 透過
parentSessionId連結到原始 session
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 │
└──────────────────────────────┘
恢復的 session 完全了解之前發生了什麼。它知道哪些檔案被編輯過、執行了哪些命令、使用者的目標是什麼。
逐字稿清理
Session 會隨時間累積。Claude Code 透過清理來管理:
- 近期 session 保持完整以便快速恢復
- 較舊的 session 可能會壓縮或裁剪其逐字稿
- 非常舊的 session 最終會從磁碟移除
- 專案目錄 以專案路徑的雜湊值區隔,保持不同專案的 session 分開
儲存位置使用專案絕對路徑的雜湊值:
~/.claude/projects/
abc123def/ ← hash of /Users/you/project-alpha
sessions/
settings.json
fed321cba/ ← hash of /Users/you/project-beta
sessions/
settings.json
關鍵洞見
Session 儲存將短暫的對話變成持久的工作日誌。 JSONL 格式不僅是實作細節 — 它反映了一個基本的設計哲學:對話是只附加的事件串流,而不是可變的文件。
這很重要,因為:
- 你可以恢復任何 session — 關上筆電,明天重新打開,從中斷處精確地繼續
- 子代理工作是可追蹤的 — 每個子代理建立自己的 session 並連結到父 session,所以你可以稽核發生了什麼
- 格式是人類可讀的 — 你可以打開一個 JSONL 檔案手動閱讀對話
- 崩潰恢復是內建的 — 由於每一行都是獨立有效的,寫入中途的崩潰只會丟失最後一個不完整的行
父子鏈特別強大。它意味著系統不僅能重建單一對話,還能重建一整棵工作樹 — 主 session、它產生的子代理、那些子代理產生的子代理 — 全部透過 UUID 連結。
實作範例
檢查 Session 逐字稿
你可以直接探索自己的 session 逐字稿:
# Find your project's session directory
ls ~/.claude/projects/
# List recent sessions (sorted by modification time)
ls -lt ~/.claude/projects/<project-hash>/sessions/ | head -10
# Read the first few lines of a transcript
head -5 ~/.claude/projects/<project-hash>/sessions/<session-id>.jsonl
# Pretty-print a single line to see its structure
head -1 sessions/<id>.jsonl | python3 -m json.tool
# Count how many tool calls happened in a session
grep -c '"type":"tool_use"' sessions/<id>.jsonl
# Find all file edits in a session
grep '"toolName":"Edit"' sessions/<id>.jsonl | python3 -m json.tool
建構 Session 摘要腳本
以下是一個簡單的腳本,用來摘要 session 中發生了什麼:
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
前後對比
| 沒有 Session 儲存 | 有 JSONL 逐字稿 |
|---|---|
| 每個 session 從零開始 | 恢復任何之前的 session |
| 沒有過去工作的記錄 | 每個動作的完整稽核軌跡 |
| 子代理工作不可見 | 父子鏈追蹤所有工作 |
| 崩潰丟失一切 | 只附加格式經得起崩潰 |
| 上下文只在記憶體中 | 上下文從磁碟重建 |
下一堂課
現在你了解了 session 如何被儲存和連結,第 17 堂課將探討任何 Claude Code 專案中最具影響力的檔案:CLAUDE.md 設計 — 如何設計你的專案指令以獲得最大效果,以及控制 AI 行為的三層階層結構。