Local Codex and Claude Conversation Data¶
Codex Desktop and Claude's local coding-agent workflows persist readable conversation records on macOS. These files can be inspected or archived without requesting a full account export and without copying browser profiles.
This guide covers the stores verified on August 26, 2026. Vendor formats are not public compatibility contracts, so paths and record shapes may change after application updates.
What Is Available Locally¶
Codex Desktop and CLI¶
The primary transcript store is:
Each file is newline-delimited JSON. Common top-level record types include:
session_meta: thread ID, parent thread, working directory, originator, and source information.response_item: user and assistant messages, reasoning, and tool calls.event_msg: task lifecycle, token, and tool-status events.turn_contextandworld_state: execution context and permissions.compacted: replacement history created when a long thread is compacted.
Thread titles are indexed separately:
Codex also maintains SQLite projections such as
~/.codex/thread_history_1.sqlite and ~/.codex/state_5.sqlite. They are useful
for structured queries, but rollout JSONL is the simplest source for a complete,
portable transcript. Files such as auth.json and config.toml are not
conversation data and should not be copied into an archive.
Codex creates additional rollout files for guardian reviews and other
subagents. The newest file by modification time is therefore not always the
newest human-facing thread. A main thread normally has
session_meta.payload.thread_source set to user; a subagent has a
parent_thread_id and a non-user source such as guardian_review.
Claude Desktop Coding Sessions and Claude Code¶
The canonical readable transcript store used by Claude Code and Claude Desktop's local coding-agent sessions is:
The project directory encodes the original working directory. For example:
Main sessions are stored directly in that directory. Agent children are usually
under a subagents/ directory.
Useful record types include:
queue-operationwithoperation: "enqueue": a prompt submitted by the person using Claude.userandassistant: normalized conversation records with a nestedmessageobject.- Tool-use and tool-result records.
- Session, bridge, attachment, and queue metadata.
~/.claude/history.jsonl may contain interactive command history, but it is not
a substitute for project transcripts.
Claude Desktop also writes metadata under:
The claude-code-sessions/ JSON files found there can identify a corresponding
cliSessionId, but they do not necessarily contain message bodies; sessions
marked transcriptUnavailable have none locally. Local Cowork sessions keep
their own Claude Code home, so their full transcripts are under
local-agent-mode-sessions/<account>/<org>/local_<id>/.claude/projects/ and use
the same JSONL format. Cloud sessions (claude.ai/code and remote Cowork) and
ordinary claude.ai chats are not stored locally in full; Chromium IndexedDB or
LevelDB holds only fragments. claude.ai rejects automated browsers, so signed-in
browser capture is not available for it. Instead:
- Import a Claude account export (Settings → Privacy → Export data) with
aesop import claude_web ~/Downloads/<export>.zipfor claude.ai chats. - Bring a cloud coding session to this Mac with
claude --teleport <session-id>from a checkout of its repository. The resumed session is saved as Claude Code project JSONL, which theclaude_codecollector archives.
macOS Access¶
Reading files in ~/.codex and ~/.claude normally requires only access to the
current user's home directory. If macOS denies access:
- Open System Settings → Privacy & Security → Full Disk Access.
- Grant access to the terminal or Python executable performing the read.
- Quit and reopen that application after changing the permission.
JSONL rollouts are append-only in normal use. Reading them while an agent is working is generally safe, but the final line may still be in progress. Wait for the current response to finish before copying a stable snapshot. Close the source app before directly copying mutable SQLite, IndexedDB, or LevelDB stores.
Locate the Newest Main Codex Thread¶
This read-only script ignores subagent rollouts and prints the most recently modified user thread:
python3 - <<'PY'
import json
from pathlib import Path
candidates = []
for path in Path.home().glob(".codex/sessions/*/*/*/*.jsonl"):
try:
with path.open(encoding="utf-8") as handle:
first = json.loads(handle.readline())
except (OSError, json.JSONDecodeError):
continue
payload = first.get("payload", {})
if first.get("type") == "session_meta" and payload.get("thread_source") == "user":
candidates.append(path)
if not candidates:
raise SystemExit("No main Codex rollout was found.")
print(max(candidates, key=lambda path: path.stat().st_mtime))
PY
To resolve a thread title, read ~/.codex/session_index.jsonl and select the
last entry whose id matches the rollout's session ID. The index can contain
multiple titles for one thread as Codex renames it.
Read the Last Human Prompt From Codex¶
The following script selects the newest main rollout and prints the last
top-level user message. Requiring a top-level response_item avoids mistaking
older messages embedded in a compacted.replacement_history record for the
latest prompt.
python3 - <<'PY'
import json
from pathlib import Path
main_threads = []
for path in Path.home().glob(".codex/sessions/*/*/*/*.jsonl"):
try:
with path.open(encoding="utf-8") as handle:
first = json.loads(handle.readline())
except (OSError, json.JSONDecodeError):
continue
payload = first.get("payload", {})
if first.get("type") == "session_meta" and payload.get("thread_source") == "user":
main_threads.append(path)
path = max(main_threads, key=lambda item: item.stat().st_mtime)
latest = None
with path.open(encoding="utf-8") as handle:
for line in handle:
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
payload = event.get("payload", {})
if (
event.get("type") == "response_item"
and payload.get("type") == "message"
and payload.get("role") == "user"
):
text = [
block.get("text", "")
for block in payload.get("content", [])
if block.get("type") == "input_text"
]
if text:
latest = "\n".join(text)
print(latest or "No user prompt was found.")
PY
Locate the Newest Main Claude Coding Session¶
This script excludes child agents and prints the newest project transcript:
python3 - <<'PY'
from pathlib import Path
paths = [
path
for path in Path.home().glob(".claude/projects/**/*.jsonl")
if "subagents" not in path.parts
]
if not paths:
raise SystemExit("No Claude project transcript was found.")
print(max(paths, key=lambda path: path.stat().st_mtime))
PY
Modification time is a practical discovery hint, not authoritative chronology. Use record timestamps when comparing sessions that may have been copied, restored, or touched by a later process.
Read the Last Human Prompt From Claude¶
For local coding sessions, the last non-empty enqueued queue operation is the least ambiguous representation of the last submitted prompt:
python3 - <<'PY'
import json
from pathlib import Path
paths = [
path
for path in Path.home().glob(".claude/projects/**/*.jsonl")
if "subagents" not in path.parts
]
path = max(paths, key=lambda item: item.stat().st_mtime)
latest = None
with path.open(encoding="utf-8") as handle:
for line in handle:
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if (
event.get("type") == "queue-operation"
and event.get("operation") == "enqueue"
and event.get("content")
):
latest = event["content"]
print(latest or "No enqueued user prompt was found.")
PY
Claude may also encode tool results as user-role messages. Using
queue-operation prevents those machine-generated records from being reported
as human prompts.
Archive and Render With Aesop¶
From the Aesop-Agentic repository:
aesop collect --source codex --json
aesop parse --source codex --json
aesop collect --source claude_code --json
aesop parse --source claude_code --json
Snapshots appear under:
Human-readable documents appear under:
The source stores remain untouched. Aesop's explicit allowlists omit Codex authentication files and Claude credential files. Run local verification after collection and parsing:
This command also checks configured backup destinations. A destination mismatch means the local archive has changed since its last mirror; it does not by itself mean the source snapshot or parsed document is corrupt.
Known Limitations¶
- Codex thread titles live in a separate index and are not always merged into the rendered title.
- Codex compacted replacement history requires special handling to reconstruct prior turns without duplicates.
- Codex SQLite projections may contain records not decoded by the current parser; their raw snapshots are retained as opaque coverage.
- Claude Desktop metadata can point to a Claude Code session without containing transcript bodies.
- Ordinary web chats from claude.ai are outside the local coding-session workflow and may require a separate supported export or authenticated capture.
- Local files can include private prompts, source code, tool output, paths, and secrets printed during a session. Keep snapshots in private, access-controlled storage.