Skip to content

Where Codex stores session history ​

Codex writes one JSONL file per session, filed by the date it started:

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

A real one looks like this:

~/.codex/sessions/2026/03/20/rollout-2026-03-20T09-55-16-019d08f4-37fe-7090-96a6-100d7fbe09df.jsonl

Archived sessions move to a sibling directory:

~/.codex/archived_sessions/

Codex also keeps a schema-versioned state database at ~/.codex/state_<N>.sqlite, where <N> climbs with each schema change. The highest-numbered file is the live one.

The path does not tell you the project ​

The directory hierarchy is dates, not projects. The working directory is recorded in session_meta.payload.cwd inside the file. So "show me every Codex session for this repo" cannot be answered from a directory listing. Something has to open each file and read the cwd out of it.

Two kinds of record ​

Codex's JSONL is closer to an event stream than a message log, and it carries each turn twice at different levels of abstraction:

RecordWhat it is
event_msgThe high-level conversation event. Clean text, already typed
response_itemThe raw model API item: tool calls, multi-part content arrays

Use event_msg for readable text, and response_item when you need tool-call detail or attachments.

Images arrive split across both ​

A single user turn that contained a pasted image is written as two records. The response_item with role: user carries the input_image block with the actual image data, and the event_msg.user_message carries the clean typed text. Neither is complete on its own. To render the bubble the way it happened, you have to buffer the image and attach it to the matching text event.

Reading a Codex session from the terminal ​

Print the conversation text:

bash
jq -r 'select(.type=="event_msg")
  | .payload // . | select(.type=="user_message" or .type=="agent_message")
  | .message // .text' \
  ~/.codex/sessions/2026/03/20/rollout-*.jsonl

Find which project a session belongs to, without opening it in an editor:

bash
jq -r 'select(.type=="session_meta") | .payload.cwd // empty' SESSION.jsonl | head -1

Group every session by project. The directory layout will not give you this listing, so the loop has to open every file:

bash
for f in ~/.codex/sessions/*/*/*/rollout-*.jsonl; do
  jq -r 'select(.type=="session_meta") | .payload.cwd // empty' "$f" 2>/dev/null | head -1
done | sort | uniq -c | sort -rn

How do I resume this session? ​

Run codex resume SESSION_ID from the project's directory with Codex installed, or use the app's terminal or in-app chat. The app excludes archived sessions from the ordinary list; open its archived-session view to find them.

Source and limitations ​

Implementation: Codex adapter. Upstream: Codex documentation.

Reference scope: checked against the Sessions Viewer 0.6.0 adapters on 2026-10-05, not a compatibility guarantee for every CLI release. Shell examples use macOS/Linux paths and require jq (or sqlite3 for opencode). Replace placeholders with real values and use your configured data root. Text extraction does not replay tools, images or the active branch of a session tree. Command regressions use downloadable synthetic samples, not private sessions. Check the capability and evidence matrix for tested scope. Browsing is distinct from resuming or editing data; see privacy and data handling.

The viewer reads the home-directory layout above; a CLI writing to a different CODEX_HOME is not automatically covered. See missing-session troubleshooting.

Or open it in an app ​

Follow the Codex project, archive, export and resume workflow for step-by-step use and manual-vs-GUI tradeoffs.

Sessions Viewer does that grouping for you and caches it, pairs split image records back together, and can show subscription quota windows when the authenticated account exposes them. Quota is not a token-cost invoice; see statistics. It also reads Claude Code, Grok Build, Kimi Code, Pi, Antigravity CLI and opencode.