Where Claude Code stores session history
Claude Code keeps every session as a single JSONL file under your home directory:
~/.claude/projects/<project-dir>/<session-id>.jsonl<session-id> is the UUID you pass to claude --resume. Some CLI versions also maintain sessions-index.json; Sessions Viewer can scan the JSONL files without that index.
The project directory name
Claude Code derives the directory name from the absolute path of the project by replacing every / with -, including the leading one:
/Users/me/apps/blog → -Users-me-apps-blogWindows separators and the drive colon are also replaced (E:\work\blog → E--work-blog). This encoding is not reversible when names contain dashes. Prefer the record's cwd to identify the project rather than guessing from the folder name.
What a Claude Code record looks like
One JSON object per line, keyed by type:
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]}}
{"type":"assistant","message":{"role":"assistant","content":[{"type":"thinking","thinking":"..."},{"type":"tool_use","name":"Edit","input":{}}]}}The conversation lives in message.content, an array of blocks:
Block type | Contents |
|---|---|
text | Plain message text |
thinking | Extended thinking, when the model produced any |
tool_use | A tool call: name plus an input object |
tool_result | The result, matched to a call by tool_use_id |
image | source.base64 for pasted images, source.url for referenced ones |
There are other type values at the record level too, such as custom-title when a session has been renamed.
Structured diffs
When a tool_result comes back from a file edit, it often carries a structuredPatch field: the diff in hunk form rather than as a blob of text. That field is what makes it possible to render an edit as a real diff.
Reading a Claude Code session from the terminal
Extract user and assistant text (not tools or images):
jq -r 'select(.type=="user" or .type=="assistant")
| .message.content
| if type=="string" then . else .[]? | select(.type=="text") | .text end' \
~/.claude/projects/-Users-me-apps-blog/<session-id>.jsonlList which tools a session called, most used first:
jq -r '.message.content[]? | select(.type=="tool_use") | .name' \
~/.claude/projects/*/*.jsonl | sort | uniq -c | sort -rnFind the sessions that touched a particular file:
grep -l "src/api.ts" ~/.claude/projects/*/*.jsonlHow do I resume this session?
Run claude --resume SESSION_ID from the project's directory with Claude Code installed. Or use the app's terminal or in-app chat. Continuing may contact the configured provider and append new turns.
Source and limitations
Implementation: Claude Code adapter. Upstream: Claude Code documentation.
Reference scope: checked against the Sessions Viewer 0.6.0 adapters on 2026-10-05, not a compatibility guarantee for every CLI release. Shell examples use macOS/Linux paths and require
jq(orsqlite3for opencode). Replace placeholders with real values and use your configured data root. Text extraction does not replay tools, images or the active branch of a session tree. Command regressions use downloadable synthetic samples, not private sessions. Check the capability and evidence matrix for tested scope. Browsing is distinct from resuming or editing data; see privacy and data handling.
Missing a session? Follow the discovery checklist.
Or open it in an app
Follow the Claude Code search, export and resume workflow for step-by-step use and manual-vs-GUI tradeoffs.
Sessions Viewer reads these files directly. Supported recorded thinking blocks, paired tools, structuredPatch diffs and images appear in a readable view. History browsing does not rewrite the originals. With Claude Code selected, ⌘⇧F searches titles and saved user prompts across its projects; see search scope and display limits. It also reads Codex, Grok Build, Kimi Code, Pi, Antigravity CLI and opencode.
