7 August 2026

How to search your Claude Code and Codex history without another subscription

The thing I was looking for was something about retry logic, and I was fairly sure I had asked Codex about it. Or Claude Code. That week I had been going back and forth between the two on the same project, which meant the conversation I wanted could be in either place, and I did not actually know which.

I did what I always do first, which is open the tool and scroll. That works for a while. Mine stopped working somewhere around session thirty, in whichever tool it was.

What got me out of that was remembering something dumb and obvious: these are just files. Nobody hosts your Claude Code sessions for you. Nobody hosts Codex's either. They are sitting in your home directory right now, in plain text, and the tool's own search box is not the only way in. It is not even a particularly good way in, it is just the one they show you.

Where they actually are

Claude Code keeps one file per session, and the directory it lives in is named after the project, with the slashes swapped for dashes:

~/.claude/projects/<your-project-path-with-dashes>/<session-uuid>.jsonl

Codex splits things up by date instead of by project, and keeps a separate tree for older sessions:

~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<uuid>.jsonl
~/.codex/archived_sessions/rollout-<timestamp>-<uuid>.jsonl

Both are JSONL, meaning one JSON object per line, meaning both are plain text and both are things grep already knows how to read. You do not need a parser to start. You need two minutes and a keyword.

grep -ril "retry" ~/.claude/projects/
grep -ril "retry" ~/.codex/sessions/ ~/.codex/archived_sessions/

That gets you the file. Whichever one comes back is your answer to "which tool was it," a question the tools themselves cannot answer since neither knows the other exists.

Getting the words out instead of the JSON

grep will hand you the whole line, brackets and field names and all, which is readable but barely. Claude Code's structure is at least consistent enough to pull apart with jq. A user message is a plain string:

jq -r 'select(.type=="user") | .message.content' session.jsonl

and an assistant reply is a list of blocks, where the text you want has its own type:

jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="text") | .text' session.jsonl

I checked both of those against a real session on my own machine before putting them here, rather than guessing at the shape from memory.

Codex I will be honest about: I did not get as clean an answer. Every line carries a type field, response_item or event_msg or turn_context among others, and the actual text sits somewhere inside payload depending on which of those it is. I did not want to publish a jq filter I had not actually confirmed against enough files to trust it. grep still gets you to the right file and the right line, you are just reading one more layer of JSON around it than you are with Claude Code.

There is also a smaller file, ~/.codex/history.jsonl, flat rather than split by session. On my machine it only had two lines in it, which is fewer than the number of sessions I have actually run, so I am not confident it is a complete record of anything. Worth a quick check. I would not build a habit around it.

Where this stops being enough

Two things about doing it this way, and neither is really a complaint, since grep was never built for this.

The first is that it only finds what you can already spell correctly. If you asked about "the container getting stuck" and you search for "hanging," grep finds nothing, because grep does not know those are the same problem. You end up guessing at your own past phrasing, which is a strange thing to have to do.

The second is that it is always one tool at a time. There is no version of the two commands above that searches both stores and gives you one merged, ranked list, because grep has no idea a "session" in one tool's JSONL means the same thing as a "session" in the other's. Every search is really two searches, and you have to remember to run both.

For a while that was fine. It is what I did for months before I built anything. It is also the reason I ended up building something: I got tired of running the same grep twice, in two different syntaxes, then squinting at raw JSON to find the sentence I actually wanted.

Where I ended up

Inventory reads both of these stores, plus a few other tools' besides, into one local index and lets you search it by keyword or by meaning, whichever one actually finds the thing. No account, nothing sent anywhere, and it is a one-time price rather than another monthly line item on top of whatever you are already paying for the AI tools themselves. It does not do anything grep could not technically do. It just does it across every tool at once, and it understands "hanging" and "stuck" are the same complaint even when you do not phrase them the same way twice.

If grep and a keyword get you what you needed today, that is genuinely fine, and now you know where to point it. If you are doing that every week across two or three tools, you already know which one you are.

Exact paths and formats for the other four tools I index are on where each AI coding tool stores its chat history, if Cursor or Zed or Kiro or Antigravity are also in your rotation.

All posts · Where each tool stores its history