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 full-text search across your entire Claude Code history, with one keypress to jump back into any conversation.
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 cd
s 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:
--disabled
change:reload
trick — 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.
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":
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.
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.