Skip to content

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-blog

Windows 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:

json
{"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 typeContents
textPlain message text
thinkingExtended thinking, when the model produced any
tool_useA tool call: name plus an input object
tool_resultThe result, matched to a call by tool_use_id
imagesource.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):

bash
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>.jsonl

List which tools a session called, most used first:

bash
jq -r '.message.content[]? | select(.type=="tool_use") | .name' \
  ~/.claude/projects/*/*.jsonl | sort | uniq -c | sort -rn

Find the sessions that touched a particular file:

bash
grep -l "src/api.ts" ~/.claude/projects/*/*.jsonl

How 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 (or sqlite3 for 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.