# 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)
└── <munged-cwd>/ one dir per project cwd; / . \ : → -
├── <uuid>.jsonl one session = one JSONL file
├── <uuid>/ per-session side-files (not sessions):
│ ├── subagents/ tool-results/ ...
└── <uuid>.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 `<session-uuid>.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.
| `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<Block>` 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` |
| `<command-name>` markup | Slash command the user ran, as XML-ish tags filling a body | `Block::ToolUse` with `Tool::Command` |
| `<local-command-stdout>` markup | What the command printed | `Block::ToolResult` paired via `parentUuid` |
| `<local-command-caveat>` 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 `<command-args>` 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 `</local-command-stdout>` could truncate or forge an envelope on re-read.
txcript writes with a bijective backslash escape (`</tag>` → `<\/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.
- **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.