Dev.to · 3 min read

osFoundry 如何在磁碟上找到你的 Claude Code 和 Codex 工作階段,用 Python 重做一遍

osFoundry 如何在磁碟上找到你的 Claude Code 和 Codex 工作階段,用 Python 重做一遍

~/.claude/projects/-Users-alex-code-shop-api/c5e1a7b2-9d40-4f3e-8b16-2a7c0e9d4f58.jsonl ~/.claude/projects/-Users-alex-code-shop-api/c5e1a7b2-9d40-4f3e-8b16-2a7c0e9d4f58/subagents/agent-.jsonl ~/.codex/state_5.sqlite -> table threads (id, rollout_path, cwd, git_branch, title, …) ~/.codex/sessions/2026/11/04/rollout-2026-11-04T10-00-00-.jsonl $ claude --resume c5e1a7b2-9d40-4f3e-8b16-2a7c0e9d4f58 $ codex resume 019a2f1c-7d0e-7b31-9c4e-5a1f0e2d8b63 CLI 和工作階段選擇器之間的約定,就只有這些。Claude Code 和 Codex 各自把工作階段寫成你家目錄裡的一般檔案,也都接受一個 id 來恢復某個工作階段。osFoundry 桌面 App 裡的 External agents 畫面,就是建立在這些檔案之上的讀取器(Rust,runtime/session_inventory.rs 和 runtime/external_sessions.rs)。它會列出十二個 App 的工作階段,用標題、資料夾、分支、App 和 id 比對搜尋字串,再在終端機裡執行對應的 resume 指令。 下面把其中 Claude Code 和 Codex 的部分,重寫成一支 150 行的 Python 腳本,並在撰寫本文的那台 Mac 上,對真實的工作階段資料夾實際跑過。本文任何地方都不會印出對話紀錄的內容。所有數字都是數量、大小或耗時。 一個 Claude Code 工作階段在磁碟上長什麼樣子? 每個工作階段一個 JSONL 檔,檔名是工作階段 id,放在以工作目錄命名的資料夾裡。資料夾名稱就是絕對路徑,把所有非英文字母、非數字的字元都換成 -: re.sub(r"[^A-Za-z0-9]", "-", cwd) # /Users/alex/code/shop-api -> -Users-alex-code-shop-api 用這台機器上的 40 份對話紀錄驗證:40 個資料夾裡 40 個都符合這條規則。這條規則會遺失資訊(my.app 和 my-app 會撞名,/ 和 . 也一樣),所以千萬不要反解資料夾名稱,改從紀錄裡讀 cwd。 子代理(Task 工具的 Explore 等等)會把自己的對話紀錄寫在 /subagents/agent-.jsonl。這裡共有 129 份。沒有人會去恢復這些工作階段,osFoundry 也會略過所有檔名主幹以 agent- 開頭的檔案。 每一行是一筆帶有 type 的紀錄。統計 40 個檔案各自前 400 行的類型: type 筆數 建索引時重要的 key assistant 4,060 requestedModel, effort, gitBranch, message.model attachment 3,197 cwd, gitBranch user 2,469 cwd, gitBranch, permissionMode, isSidechain, message.content, entrypoint custom-title 922 customTitle agent-name 827 agentName system, mode, queue-operation, file-history-, api-request, … 2,000+ 不需要 對話紀錄(user、assistant、attachment、system)都帶有 sessionId、cwd、gitBranch、timestamp 和 version。message.content 不是字串就是 block 的陣列;在這些行裡,block 類型分別是 tool_result 2,261、tool_use 2,223、thinking 1,224、text 754。標題來自 custom-title 紀錄,或者在會寫入它的版本中,來自帶有 aiTitle 的 ai-title 紀錄。這裡每個檔案都有 custom-title,沒有一個有 ai-title,所以兩種都要讀。 為什麼不乾脆把每個檔案整個解析? 因為檔案很大。這裡 40 份頂層對話紀錄加起來有 19.4 GB。中位數是 168 MB,最大的 2.07 GB,第 90 百分位數 1.94 GB。 把中位數那個檔案逐行丟進 json.loads 串流處理,花了 0.61 s(11,558 行,在 Apple Silicon Mac 上約 279 MB/s,page cache 已經熱了)。照這個速度,40 個檔案全部解析一遍大約要 70 s,而且在那之前畫面上什麼都還沒顯示。 清單需要的資訊都在檔案的頭尾兩端。對 40 個檔案全部量過: 第一筆含 cwd 的紀錄,最晚出現在第 2 行(中位數 2) 第一個 gitBranch 和第一筆 user 紀錄:最晚第 2 行 40 個檔案中有 40 個,在最後 512 KiB 內有標題紀錄 前 80 行沒有任何一行超過 256 KiB 所以索引只讀前 80 行和最後 512 KiB。osFoundry 也是這麼做的:session_inventory.rs 裡是 HEAD_LINES = 80 加上每行 256 KiB 的上限,external_sessions.rs 裡是 SETTINGS_TAIL = 512 * 1024,用來讀最新的模型、effort、核准模式和分支。讀完 40 個檔案的頭尾花了 86 ms。 HEAD_LINES, LINE_CAP, TAIL_BYTES = 80, 256 * 1024, 512 * 1024 def head(p, n=HEAD_LINES): out = [] with open(p, "rb") as f: for _ in range(n): line = f.readline(LINE_CAP + 1) if not line: break if len(line) > LINE_CAP: # a pasted file or image: skip the rest of it while line and not line.endswith(b"\n"): line = f.readline(1 nbytes else lines # first piece is a cut line 有上限的 readline 很關鍵。使用者貼上一張圖片的那一輪,就是一整行好幾 MB,單純的 for line in f 會把整行讀進記憶體,結果只是為了丟掉它。 選擇器真正需要哪些欄位? 五個,再加一個時間:App、id、資料夾、分支、標題。檔頭提供 cwd、第一個 gitBranch,以及從第一則真正的使用者訊息取出的備用標題(不能是 sidechain,也不能以 < 開頭,因為指令和 hook 的包裝都是這樣開頭的)。檔尾再用最新的標題和最新的分支覆蓋,因為一個長的工作階段,常常比它起始的那個分支活得更久。 def claude_sessions(): for proj in sorted((CLAUDE / "projects").glob("*/")): for p in proj.glob("*.jsonl"): if p.stem.startswith("agent-"): # sub-agent transcript, not resumable continue st = p.stat() cwd = branch = title = None for v in records(head(p)): cwd = cwd or v.get("cwd") branch = branch or (v.get("gitBranch") or None) if title is None and v.get("type") == "user" and not v.get("isSidechain"): t = (first_text((v.get("message") or {}).get("content")) or "").strip() if t and not t.startswith("

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News