ccthread, Reading My Claude Code Sessions From The Command Line
Jake MarshIn this post
Every Claude Code session gets saved to disk. Each one is a .jsonl file under ~/.claude/projects/, one JSON object per line, holding every message, tool call, tool result and bit of bookkeeping from that conversation. After a few months of working this way, that folder holds a lot of what I know about my own projects: why we picked one approach over another, which port something ended up on, how a tricky deploy finally got sorted out.
The trouble is that raw JSONL is miserable to read, for me and for an agent. So I made ccthread, a small CLI that reads those logs and turns them into clean markdown.
What It’s For#
The way I use it most is as a Claude Code plugin. Once it’s installed, I can just ask things like:
- “What did we decide about rate limits in that session last Thursday?”
- “Summarize my work this week, I need to update the team.”
- “Find the thread where we figured out the Sparkle deploys and turn it into a skill.”
- “What port did we land on for the local Stripe webhook?”
Claude runs ccthread find, search or show, reads the output like it would read a source file, and answers from what was said in the actual conversation. ccthread itself is only the reader. The agent does the thinking.
It’s also a regular Unix CLI if you want to poke around yourself:
ccthread find "stripe webhook" # which old thread discussed this?
ccthread show last # read the most recent session
ccthread search "rate limit" --window 2 # grep with surrounding context
ccthread list --project great-work --since 2026-04-01
ccthread info 2F0A28FA # metadata + token usage
ccthread stats --project great-work # totals across sessionsSession ids can be a UUID prefix, a file path, last, or current. Every command takes --json and --plain too, so it’s easy to pipe into other things.
Installing It#
The repo doubles as its own plugin marketplace, so installing in Claude Code is two commands:
/plugin marketplace add jakemarsh/ccthread
/plugin install ccthread@ccthreadThe plugin includes a skill that tells Claude where past conversations live and when to reach for the CLI, so phrases like “last session”, “what did we decide” or “find where we talked about” pick it up on their own. There’s also a one-line install script for macOS, Linux and Windows if you only want the command on your PATH.
Reading Really Big Files#
Session files get big. I have some over 100 MB, tens of thousands of lines each, so ccthread never loads a whole file into memory. It streams the file through a TextDecoderStream and splits lines as they arrive:
const reader = Bun.file(path).stream()
.pipeThrough(new TextDecoderStream())
.getReader();
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += value;
let nl: number;
while ((nl = buf.indexOf("\n")) >= 0) {
const raw = buf.slice(0, nl);
buf = buf.slice(nl + 1);
if (raw.trim()) yield JSON.parse(raw);
}
}The real version also strips a byte order mark, handles CRLF line endings, and reports a bad line instead of giving up on the whole file. Because everything is a generator, show can render page one of a huge session and stop reading, which is why it comes back in well under a second.
Turning Logs Into Something Readable#
A lot of what’s in these files is bookkeeping: hook progress events, attachment metadata, background queue operations. show hides those by default and keeps the parts people care about. User and assistant messages, tool calls with their results tucked right under them, API errors, the spot where a session was compacted, slash commands you ran, and permission mode changes. --verbose brings the rest back when you want it.
A few details took some digging into how Claude Code writes its logs. Large tool results don’t live in the main file at all. They overflow into a sibling tool-results/ folder next to the session, so ccthread looks there and stitches them back in. Subagent conversations are stored as sidechains, and they stay out of the way unless you pass --include-sidechains. Long tool output gets trimmed to 40 lines by default, with --tool-details full or none when you want more or less.
Finding the Current Session#
One of my favorite additions is current. If ccthread is running inside Claude Code, ccthread show current reads the session you’re in right now. That’s handy after a long conversation gets compacted and something useful from earlier is gone from the agent’s context: ccthread show current --before-last-compact brings back what was said before the compaction.
Finding the current session takes a couple of tries. ccthread first checks an environment variable, then walks up its parent processes looking for a claude process launched with --session-id or --resume in its arguments. When Claude Code was started without either, the plugin’s SessionStart hook has already written the session id and transcript path to a small file keyed by process id, and ccthread reads it from there.
One Binary Per Platform#
ccthread is Bun and TypeScript, built into a single file with bun build --compile for six targets: macOS on Apple Silicon and Intel, Linux on x64 and ARM, and Windows on x64 and ARM. I didn’t want the plugin to carry all six binaries around, so the plugin ships a tiny shell dispatcher instead. On first run it works out your platform, downloads the matching release from GitHub, checks its SHA256, caches it under the plugin’s data folder and then runs it. The version it fetches is pinned in a file that CI keeps in sync with package.json and the plugin manifest, so the plugin and the binary always match.
It also honors NO_COLOR (plus a --no-color flag), which makes CI logs a lot nicer to read.
Everything runs locally and nothing leaves your machine. It’s been fun to use: I ask about something from three weeks ago and get an answer from the actual conversation. The code is up on GitHub.