I use Claude Code a lot. Enough that when I finally counted, I had 1,063 conversations sitting in ~/.claude/projects/ — around 256 MB of JSONL transcripts.
And I could never find the one I needed.
You know the feeling: "I definitely solved this exact bug a few weeks ago… in some conversation… somewhere." But Claude Code's built-in --resume picker only matches on a session's title (its first prompt). It has no idea what was actually said inside. So the conversation where I'd figured out the gnarly thing was, in practice, gone.
So I built claude-grep: live search across your entire Claude Code history, with one keypress to jump back into any conversation.
What it does
You type, and results stream in as you go. Each row shows the matched snippet with your phrase highlighted, the conversation's working directory, and when it happened. Hit Enter and it cds into that project and runs claude --resume on the right session — you're back exactly where you left off.
Under the hood it's deliberately boring and fast:
- ripgrep does the first pass, narrowing 1,000+ transcripts to candidates in milliseconds.
- A small Python layer parses only those candidates to pull clean, highlighted snippets out of the JSONL.
-
fzf drives the UI using the
--disabled+change:reloadtrick — fzf re-runs the search on every keystroke instead of fuzzy-filtering what's already on screen.
No index to build, nothing running in the background. It just reads the files Claude Code already writes.
The bug that nearly killed it: sub-agents
When I first wired up "press Enter to resume," a bunch of conversations simply wouldn't open.
It turns out 940 of my 1,063 transcript files were sub-agent sessions — the side conversations Claude Code spawns to handle sub-tasks. They live here:
~/.claude/projects/<project>/<PARENT-UUID>/subagents/agent-*.jsonl
…and they are not resumable on their own. claude --resume agent-abc123 does nothing.
But the path gives it away: the parent session's UUID is literally the directory name above subagents/. (It's also stored in the file's sessionId field, alongside isSidechain: true.) So when a match lands inside a sub-agent transcript, claude-grep resolves it to the parent and resumes that — using the parent's working directory, so it even survives sub-agents that ran in a since-deleted git worktree.
def resumable_session(path):
parent_dir = os.path.dirname(path)
if os.path.basename(parent_dir) == "subagents":
# sub-agent transcript → resume the parent session instead
return os.path.basename(os.path.dirname(parent_dir)), True
base = os.path.basename(path)
return (base[:-6] if base.endswith(".jsonl") else base), False
Results then get de-duplicated by resumable session, so you never see two rows that open the same conversation.
Try it
You'll need fzf, ripgrep, Python 3, and the claude CLI on your PATH.
git clone https://github.com/coolcorexix/claude-grep.git
ln -sf "$PWD/claude-grep/ccfind" ~/.local/bin/ccfind # ~/.local/bin must be on PATH
ccfind
It's MIT-licensed and a single Python script — read it, fork it, rip out the parts you like.
👉 github.com/coolcorexix/claude-grep
If you live in Claude Code as much as I do, I'd love to hear what you search for first — and if you hit a transcript shape it chokes on, open an issue. There are a lot of edge cases hiding in those JSONL files.

Top comments (0)