跳至主要內容
模組 3:真實架構 4 / 6
進階 Session 16 Session Storage JSONL

Session 儲存

了解使用 UUID 父子鏈的 JSONL 逐字稿。

2026年3月20日 15 分鐘閱讀

你將學到什麼

你與 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_startSession ID、父 ID、時間戳、專案路徑標記 session 開始
message角色、內容區塊、時間戳使用者或助理訊息
tool_use工具名稱、輸入參數、tool_use_idAI 想做什麼
tool_resulttool_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 時,系統會:

  1. 列出當前專案的近期 session
  2. 讀取所選 session 的 JSONL 逐字稿
  3. 從逐字稿事件重建訊息陣列
  4. 如果恢復的上下文太大則進行壓縮
  5. 建立一個新的子 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 行為的三層階層結構。