Where every AI coding tool stores your sessions on disk

Every one of these tools writes your whole conversation to your own disk as it goes. Knowing where is useful for grepping, for backups, and for the less comfortable question of what a pasted credential is now sitting in.

The six locations

Paths are the defaults. Each one is overridable, and the environment variable that does it is in the last column, because a machine with two profiles has two of these trees rather than one.

ToolWhere the transcript livesFormatOverride
Claude Code~/.claude/projects/<project>/<uuid>.jsonlJSONL, one event per lineCHAT_RECALL_CLAUDE_HOME
Codex~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonlJSONL "rollouts", filed by dateCHAT_RECALL_CODEX_HOME
Gemini CLI~/.gemini/tmp/<sha256 of project path>/chats/Both .json and .jsonl, depending on versionCHAT_RECALL_GEMINI_HOME
OpenCode~/.local/share/opencode/opencode.dbSQLite — tables session and messageCHAT_RECALL_OPENCODE_DB
Antigravity~/.gemini/antigravity-cli/brain/<uuid>/.system_generated/logs/JSONL — read transcript_full.jsonl, not transcript.jsonlCHAT_RECALL_AGY_HOME
Cursor (CLI)~/.cursor/chats/<md5 of cwd>/<chatId>/store.dbSQLite, one file per chatCHAT_RECALL_CURSOR_HOME
Cursor (desktop)~/.config/Cursor/User/globalStorage/state.vscdbSQLite. On macOS: ~/Library/Application Support/Cursor/CHAT_RECALL_CURSOR_IDE_HOME

Four things that catch people out

Claude Code deletes them after 30 days

Anthropic's documentation is explicit: transcripts are kept "for 30 days by default to enable session resumption", and cleanupPeriodDays sets the period. So the archive you assume you have is a rolling window, and the oldest end of it is being removed while you read this.

They are stored in plaintext

Also Anthropic's word for it. Anything you pasted is readable by anything that can read your home directory — a backup agent, a sync client, another account on a shared machine.

A second profile is a second tree

CLAUDE_CONFIG_DIR makes a whole parallel root, so work done under ~/.claude-work is invisible to anything looking only at ~/.claude. The same applies to a second OpenCode database.

Two of them are not append-only

Cursor's CLI rewrites its transcript when you resume a chat, and both Cursor surfaces and OpenCode keep their data in SQLite. A tail-the-file approach works for the JSONL tools and quietly loses data on these.

What else Claude Code keeps beside the transcript

  • ~/.claude/history.jsonl — the prompts you typed
  • ~/.claude/plans/ — plan documents, flat, one file each
  • ~/.claude/todos/ and ~/.claude/tasks/<session-uuid>/ — task lists per session
  • ~/.claude/paste-cache/ — long pastes, filed by content hash
  • ~/.claude/projects/<project>/memory/ — auto memory, which is excluded from the 30-day sweep

Grepping them by hand

For one string in one tool, this is enough and you do not need anything else:

one tool, one string
$ grep -rl "pgbouncer" ~/.claude/projects/
  gives you filenames; the content is JSONL, so reading it
  means piping each hit through jq

It stops being enough at the third tool, because two of the six are SQLite and one of those rewrites itself. That is the work an index does: read all six formats, normalise them to one shape, and let the assistant query it. See how it works.

If you would rather they were not there

Deleting a transcript deletes the work along with the credential, which is usually the wrong trade — rotate first, then decide. If a whole project must never be read by anything, exclude it at the source rather than cleaning up afterwards:

excluded before anything is read
$ chat-recall exclude project ~/work/client-project
$ chat-recall exclude tool gemini

Index them free What leaves your machine