Guide

Claude Code session history: where it lives and how to search it

Every session you have ever run is already on your disk, in full. Finding anything in it is the hard part.

Updated August 2026 · applies to current Claude Code releases

Where the history lives

Claude Code writes a complete transcript of every session — your prompts, the model’s replies, every tool call and its output — to your home directory:

~/.claude/projects/<encoded-project-path>/<session-uuid>.jsonl

The project directory name is the working directory’s absolute path with the separators turned into dashes: a session run in /Users/you/dev/webapp lands under -Users-you-dev-webapp/. Each session is one file named by a UUID, and newer Claude Code versions add a <session-uuid>/subagents/ directory beside it for Task-tool transcripts. Nothing about this is documented or guaranteed — the format has changed before and will change again.

Two properties of these files matter for search. First, they are complete: costs, diffs, shell output, everything. Second, resuming a session copies the whole history into a new file, so a conversation you resumed twice exists as three overlapping files.

The built-in ways

claude --continue reopens the most recent conversation in the current project, and claude --resume shows a picker of recent ones. That solves “yesterday’s session, this repo.” It does not solve “the session where we fixed the reconnect bug, sometime in June, in one of four repos” — there is no cross-project view and no way to search by what was said.

The grep way

It’s all text, so grep works — sort of:

grep -rl "reconnect" ~/.claude/projects/ # which files mention it
grep -rn "reconnect" ~/.claude/projects/ | head # the lines themselves

Three things make this worse than it looks:

A step up is jq, which at least understands the records — for example, listing just your own prompts:

jq -r 'select(.type=="user") | .message.content
  | if type=="string" then . else (map(.text // empty) | join(" ")) end' \
  ~/.claude/projects/-Users-you-dev-webapp/*.jsonl | grep -i reconnect

That works until a record shape changes, and it is still one project, one record type, no ranking, no way to open the match in context. (For what the records actually look like, see the JSONL format guide.)

What “searchable” actually needs

The gap between grep and search is structure: an index that knows what a session is, which project it belongs to, who said what, and what a hit means — so results group by session, rank by relevance, filter by facts (tool:Bash, is:error, agent:codex, before:2026-07), and open at the matched moment with the conversation around it. That is a database job, not a text-scan job.

Turnlog searching 'reconnect': 13 hits in 2 sessions with the work's cost, refine chips showing one Claude Code and one Codex session, operator suggestions, and highlighted matches grouped by session
One query across every project and both agents — grouped, costed, one click from the moment. (Sample sessions via npx turnlog demo.)

The indexed way

Turnlog is that index

Turnlog is a free, MIT-licensed CLI that parses your entire ~/.claude/projects/ (and Codex’s ~/.codex/sessions/) into a local SQLite full-text index, served on a hardened localhost UI. Search composes operators with text, resumed sessions read as one conversation, every hit opens as a turn-by-turn replay, and your agent can query the same index over MCP. Nothing leaves your machine.

Related: what’s inside the JSONL · one file’s history across sessions · tracking costs honestly · how the other tools compare