cd /news/ai-tools/ccfind-search-all-your-claude-code-s… · home › topics › ai-tools › article
[ARTICLE · art-153594] src=belderbos.dev ↗ pub= topic=ai-tools verified=true sentiment=↑ positive

ccfind: search all your Claude Code sessions

Developer Bart Belderbos released ccfind, a Rust command-line tool published on crates.io that full-text searches Claude Code and Codex session transcripts across directories and returns the command to resume a matching session. On Belderbos's machine, ccfind searches 539 transcripts totaling 722 MB in about 0.6 seconds, using rayon for parallel search and a custom JSON parser instead of serde. The tool addresses a gap in `claude --resume`, which lists sessions only for the current directory and matches only on titles.

by read5 min views1 publishedOct 20, 2026
ccfind: search all your Claude Code sessions
Image: Belderbos (auto-discovered)

I remembered the conversation: transcribing a Rust cohort video with mlx-whisper. I didn't remember where I ran it. It turned out to be ~/Downloads, but I only found that out later: claude --resume lists sessions for the current directory and only matches on titles.

So I built a small Rust CLI for it: ccfind.

What it does #

ccfind searches the full-text history of Claude Code and Codex across directories. It ranks rare terms higher, shows matching conversations, and gives you the command to resume the session in the directory where it started.

It's on crates.io:

$ cargo install ccfind

Three useful flags:

  • --all includes tool output and reasoning,
  • --project limits the search to a project,
  • --since 7d filters by recency.

On my machine, it searches 539 transcripts totaling 722 MB in about 0.6 seconds. With serde it would be faster, but I chose my own JSON parser on purpose (more on that in a bit).

That last line is the other feature I wanted: one line to copy and paste to resume the session.

Why Claude Code sessions are hard to find #

Claude Code stores sessions under ~/.claude/projects/<encoded-dir>/. Each directory gets its own session history. Codex has a separate store and its own codex resume command.

Of course you can use grep -r, but it's limited; it doesn't give you a useful conversation title or a ready-to-run command to get back into the session.

I could have built a shell script, but with Rust I got some nice opportunities: ranking results, parallelism, traits, and using my own parser.

Two Rust patterns #

Parallel from the first commit

The search uses rayon:

let searched: Vec<Option<Found>> = files
    .par_iter()
    .filter_map(|&(agent, ref path)| {
        let jsonl = fs::read_to_string(path)
            .inspect_err(|e| eprintln!("ccfind: skipping {}: {e}", path.display()))
            .ok()?;
        let session = agent.parse(path, &jsonl, scope);
        if !filter.admits(&session) {
            return None;
        }
        Some(search(&session, &terms).map(|hit| (agent, session, hit)))
    })
    .collect();

Each transcript is read, parsed and searched on its own with no shared state, so swapping .iter() for .par_iter() was all it took.

A trait made Codex one file

Once the Claude Code version worked, I looked at what was specific to that agent: transcript locations, parsing, session IDs, and resume commands. Search, ranking, and output formatting didn't need to know any of those details.

I put the agent-specific behavior behind a trait, the Rust counterpart of a Python Protocol (see Protocol or ABC?):

pub trait Agent: Sync {
    fn name(&self) -> &'static str;
    fn transcripts(&self, home: &Path) -> io::Result<Vec<PathBuf>>;
    fn parse(&self, path: &Path, contents: &str, scope: Scope) -> Session;
    fn resume_command(&self, id: &str) -> String;
}

pub const AGENTS: &[&dyn Agent] = &[&ClaudeCode, &Codex];

Maybe this is premature abstraction since I mostly use Claude Code, but adding an agent now means one new file and three lines in src/agents/mod.rs.

I learned about &dyn Agent being a fat pointer: one pointer to the data, one to a vtable with that agent's methods. Each call looks up the method in the vtable at runtime. The structs hold no data, and the slice and vtables are built at compile time, so the registry allocates nothing.

Sync is there because rayon shares the agents across threads. The compiler enforces that they're safe to share.

An enum Agent with a match would be simpler for a closed set of agents that only I change. The trait makes more sense when you want other people to add new ones over time.

Parsing with my own cohort JSON parser #

bob-json-parser started as my capstone project in our Scripter to Rust cohort. It's a hand-written tokenizer and recursive-descent parser with PyO3 bindings (no serde).

I replaced serde in ccfind with my own parser. The commit removed 266 lines and added 132, and Cargo.lock went from 18 packages to 8.

Instead of serde derive types, I parse into JsonValue and pull out the fields I need. Unknown record types and malformed lines are skipped.

There is a performance tradeoff though. With the same 539 transcripts and release builds, serde took about 0.2 seconds. bob-json-parser took about 0.6 seconds. But that's still under 1 second and the command line feels fast.

Normally I'd take the faster one. Here I wanted to know if my parser would hold up on 722 MB of real transcripts. So far it does.

For more on the decisions behind the parser itself, see What building a JSON tokenizer taught me about Rust.

Build something real in Rust #

A cohort project stops being a toy when you use it. My JSON parser now runs inside a tool I use daily. That gives me a reason to keep improving both projects. Real transcripts test it harder than any exercise I wrote for it.

If you've been meaning to learn Rust, skip the syntax drills and build a tool you'd use this week.

If you'd like to build something similar while learning Rust, the next Scripter to Rust cohort starts in November 2026. It's six weeks with six seats. You build a JSON parser from scratch, connect it to Python with PyO3, and get every PR reviewed by a human coach.

If you use another coding agent, I'd like to see it supported in ccfind. Aider, Cursor, Gemini CLI? Adding an agent should take one new file and three lines in src/agents/mod.rs.

AI is an accelerator, not a compass. I coach developers to ship faster and understand what they ship. See the path →

── more in #ai-tools 4 stories · sorted by recency
── more on @ccfind 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/ccfind-search-all-yo…] indexed:0 read:5min 2026-10-20 · —