# Claude Code Claude Code is Anthropic's terminal coding agent. The CLI is closed source. Anthropic officially documents *where* sessions are stored, but explicitly declares the record format "internal to Claude Code" and subject to change between releases — so this document is **reverse-engineered** from observed local sessions, with `src/harness/claude_code.rs` as the authoritative mapping. The format is the least exotic of the harnesses txcript supports: each conversational line wraps a payload already in the Anthropic Messages shape, which is why the Common model (and every other harness's codec) is normalized toward it. ``` ~/.claude/projects/ ($CLAUDE_CONFIG_DIR/projects overrides) └── / one dir per project cwd; / . \ : → - ├── .jsonl one session = one JSONL file ├── / per-session side-files (not sessions): │ ├── subagents/ tool-results/ ... └── .jsonl one session file, line by line: {"type":"summary","summary":…,"leafUuid":…} title for --resume {"type":"user", …envelope…, "message":{…}} ┐ conversation, linked by {"type":"assistant",…envelope…, "message":{…}} ┘ parentUuid → uuid {"type":"system","subtype":"local_command",…} slash-command output (newer CLIs) {"type":"file-history-snapshot"|"attachment"|…} bookkeeping ``` ## On disk Sessions live under `~/.claude/projects/`, or `$CLAUDE_CONFIG_DIR/projects` when that variable is set and non-empty. Each project working directory gets one subdirectory whose name is the cwd with `/`, `.`, `\`, and `:` each replaced by `-` (`/Users/alice/src/app` → `-Users-alice-src-app`); the official docs describe this more broadly as "non-alphanumeric characters replaced by `-`". Inside, each session is a single JSONL file named `.jsonl`. A session may also own a sibling directory of the same uuid holding side-files (`subagents/`, `tool-results/`, `workflows/`); these are not sessions. txcript's `ClaudeStore::discover` walks the root recursively collecting `*.jsonl` files, skipping `subagents` and `tool-results` directories and symlinked directories (symlinked files still list). Metadata comes from a shallow scan that never builds message payloads; unreadable files are skipped, a missing root yields no sessions, and an empty `sessionId` falls back to the filename stem. ## Dissection of a transcript Each line is one JSON object tagged by `type`. Known types parse into typed records; everything else is preserved verbatim so a native load/save round-trips byte-losslessly. | Their name | What it is | Maps to | |---|---|---| | `user` / `assistant` line | Envelope (`uuid`, `parentUuid`, `timestamp`, `sessionId`, `cwd`, `gitBranch`, `version`) wrapping a `message` | `Message` with `Role::User` / `Role::Assistant` | | `message` | An Anthropic Messages API message; `content` is a string or block array | `Vec` on the `Message` | | `text` block | Plain prose | `Block::Text` | | `thinking` block | Reasoning with opaque `signature` | `Block::Thinking` | | `tool_use` block | Tool call: `id`, `name`, `input` | `Block::ToolUse` (typed `Tool`, else `Tool::Raw`) | | `tool_result` block | Output paired by `tool_use_id`, rides on a user line | `Block::ToolResult` | | `image` block | Base64 inline image | `Block::Image` | | `stop_reason`, `usage` | Anthropic stop reason and token accounting | `StopReason`, `Usage` | | `` markup | Slash command the user ran, as XML-ish tags filling a body | `Block::ToolUse` with `Tool::Command` | | `` markup | What the command printed | `Block::ToolResult` paired via `parentUuid` | | `` markup | Fixed boilerplate, regenerated by the CLI | dropped | | `summary` line | Session title with a `leafUuid` anchor | `Meta.title` (lowest precedence) | | `custom-title` / `agent-name` line | User-set or agent-derived title | `Meta.title` (custom-title wins) | | `system` line | Bookkeeping (`turn_duration`, `away_summary`, hook notices…) — except `subtype: "local_command"`, which carries command envelopes on newer CLIs | envelopes → command blocks; rest → `Record::Other` | | `attachment`, `file-history-snapshot`, `queue-operation`, `mode`, `last-prompt`, `ai-title`, … | Other bookkeeping line types | `Record::Other` (round-trip only, no Common turn) | Threading is a `parentUuid` → `uuid` chain rather than file order; txcript reads in file order and uses the chain only to pair local-command output to its command. Slash-command markup has appeared on `user` lines (older CLIs) and on `system`/`local_command` lines (newer), and only a body that is *entirely* envelope markup counts — a message that merely quotes the tags stays plain text. Session metadata (`Meta`) is extracted from the first line that carries each field: `sessionId`, `cwd`, `gitBranch`, `version` from user lines, `model` from assistant lines, the earliest `timestamp` as session start. A synthetic assistant line (real files add more envelope keys — `isSidechain`, `requestId`, `userType`…, all preserved in `extra`): ```json {"type":"assistant","uuid":"b7e2…","parentUuid":"a1f4…","sessionId":"3f2a…", "timestamp":"2026-08-10T12:00:05.123Z","cwd":"/Users/alice/src/app", "gitBranch":"main","version":"2.1.230", "message":{"role":"assistant","model":"claude-fable-5", "content":[{"type":"text","text":"Reading the parser now."}, {"type":"tool_use","id":"toolu_01","name":"Read", "input":{"file_path":"/Users/alice/src/app/main.rs"}}], "stop_reason":"tool_use", "usage":{"input_tokens":1200,"output_tokens":45,"cache_read_input_tokens":900}}} ``` ## Caveats - **The format is officially unstable.** Anthropic says so outright; observed drift includes local-command envelopes migrating from `user` to `system` lines, tag order and `` presence varying, and new line types appearing per release (`ai-title`, `file-history-delta`, `pr-link` are recent). Unknown lines survive as `Record::Other`, so native round-trips stay lossless; Common conversion drops them. - **Hostile envelope payloads.** The markup has no native quoting, so a payload containing `` could truncate or forge an envelope on re-read. txcript writes with a bijective backslash escape (`` → `<\/tag>`) and unescapes on parse; ANSI escapes are stripped from command stdout. - **Round-trip lossiness.** Common → native regenerates lines with deterministic UUIDv5 entry ids: bookkeeping lines, envelope `extra` keys, and unmodeled block types are gone after a cross-model trip (same-harness native trips keep everything). Rewritten `tool_result.content` must be a string or block array — anything else fails a `claude --resume` load — so bare JSON is flattened to its compact text. - **Tool inputs.** The Messages API requires `tool_use.input` to be an object, including calls already in the conversation history. Common → Claude Code wraps non-object inputs, such as Codex freeform tool text, as `{"input": }`. Existing objects keep their shape. Reading that export into Common retains the wrapper; native load/save keeps the original records untouched. - **Resume anchoring.** A leading `summary` line's `leafUuid` must name a real user/assistant line in the file, or Claude Code reports the whole session missing; txcript anchors generated summaries to the last real turn. - **Artifacts.** Claude Code records a published local file as an `Artifact` tool call with `file_path` and `description`, followed by its tool result. txcript maps that pair through Common's artifact block. When a Common artifact carries inline bytes, the local writer materializes it beneath the generated session before producing this native tool shape. - **Malformed input.** Invalid JSON lines are skipped; a known-type line whose body fails its schema degrades to `Record::Other` rather than failing the file. Title precedence is `custom-title` > `agent-name` > first `summary`; the newer `ai-title` line is passed through but not read. ## References - Anthropic, "Manage sessions", https://code.claude.com/docs/en/sessions (accessed 2026-08-10) — documents the storage path, `CLAUDE_CONFIG_DIR`, retention (`cleanupPeriodDays`), and states the entry format is internal and version-unstable. - No official specification of the record format exists, and Claude Code is closed source — no upstream permalink is possible. This document is reverse-engineered from real local sessions (CLI versions 0.144 through 2.1.227) and from txcript's parser. - Authoritative mapping: `src/harness/claude_code.rs`, exercised by `tests/integration/claude_code.rs`. - Last verified: 2026-08-10, against src/harness/claude_code.rs and real local sessions.