Codex CLI history: where it is, how to read it, how to search it

every session is on disk; Codex lists them, it does not search them

Codex keeps every session you run. What it does not have is a way to ask which session had the fix, or to look at them outside the resume picker. The files are plain enough that jq and sqlite3 go a long way, and a week in, one detail breaks the obvious grep.

Where Codex keeps it

Everything is under ${CODEX_HOME:-~/.codex}:

  • sessions/YYYY/MM/DD/rollout-<time>-<id>.jsonl: one file per session, the whole conversation, appended as it runs. The first line is session_meta, with the session id and the working directory.
  • After seven days Codex compresses a rollout in the background to rollout-….jsonl.zst. Codex reads both forms. Anything that looks for *.jsonl stops seeing sessions older than a week.
  • archived_sessions/: sessions you archived, same format.
  • history.jsonl: your prompts only, one line each, with the session id. No replies, no commands.
  • state_5.sqlite: the session list the resume picker reads, with title, working directory, git branch, token count and an archived flag. The number in the name moves with Codex releases.

What Codex gives you

codex resume                 # picker, sessions started in this directory
codex resume --all           # every directory
codex resume --last          # straight into the most recent one
codex resume --include-non-interactive   # codex exec runs too
codex fork                   # a copy of an old session to continue from
codex archive <id>           # hide it; codex unarchive brings it back
codex delete <id>            # gone for good

The picker shows titles and first messages. It does not look inside the conversations, and it only knows about Codex.

Reading it without any tool

The last few sessions, with where they ran:

for f in $(ls -t ~/.codex/sessions/*/*/*/rollout-*.jsonl | head -10); do
  jq -r 'select(.type=="session_meta") | .payload | "\(.timestamp[:16])  \(.cwd)  \(.id)"' "$f"
done

The same list from the picker's database, read-only:

sqlite3 -readonly ~/.codex/state_5.sqlite "
SELECT datetime(updated_at, 'unixepoch', 'localtime'), substr(title, 1, 50), cwd
FROM threads WHERE archived = 0 ORDER BY updated_at DESC LIMIT 20;"

What you typed in one session, from the prompt log. It holds interactive sessions only; prompts given to codex exec are not in it:

jq -r 'select(.session_id=="<id>") | .text' ~/.codex/history.jsonl

Which sessions mention something, including the compressed ones:

grep -l "pgbouncer" ~/.codex/sessions/*/*/*/rollout-*.jsonl
zstdgrep -l "pgbouncer" ~/.codex/sessions/*/*/*/rollout-*.jsonl.zst

That works for one word you remember exactly. It gets slow over months of history, has no ranking, and misses the session where you said it differently.

Searching it

deja-vu indexes these files, compressed ones and archived ones included, with the history of every other agent on the machine. It only reads them.

deja index
deja --harness codex "pgbouncer prepared statement"   # ranked, with the session id
deja last 20 --harness codex                           # recent sessions, any directory
deja show <id>                                         # the conversation
deja resume <id>                                       # the command that reopens it in Codex
deja view                                              # all of it as one local HTML page

Bare deja opens the same search as a screen that updates as you type: a narrows it to Codex, ↵ reads a session at the match, r reopens it in Codex.

Drop --harness codex and the same search covers Claude Code, Cursor, opencode and the rest, which is usually where the answer turns out to be. Credentials are stripped as the index is built. A session deja has indexed stays searchable after codex delete or a cleanup; deja forget removes it on purpose.

To have Codex look this up itself at the start of a session, rather than you running the search, see memory for Codex.

On more than one machine

Codex keeps its history local, and codex resume only lists what is on this disk. deja can carry its index between machines over SSH, with no service in between:

deja sync ssh devbox        # push this machine's sessions there
deja sync ssh devbox --both # and pull its sessions here

What arrives is searchable with deja on the other side. It does not add sessions to the other machine's resume picker. Across machines has the details.

Deleting Codex sessions · Where every agent stores history · Resuming a session

Found this useful? Star deja-vu on GitHub.