Import OpenAI Codex conversation history into Claude Code — transcripts, not just config.
Python
0
2 commits
updated Sep 18, 2026
Import OpenAI Codex conversation history into Claude Code, so old Codex threads
show up in /resume and can be continued.
Codex can import Claude sessions. Claude Code's own /import only moves config
(AGENTS.md, subagents, prompts, MCP servers) — it has no path for transcripts.
This closes that gap.
python3 src/codex_import.py list # what's importable
python3 src/codex_import.py import --all --dry-run # see what would happen
python3 src/codex_import.py import --all # do it
python3 src/codex_import.py import --session 01a0aa55 # just one (id prefix)
That covers the CLI: claude in the session's cwd, then /resume.
For the desktop app, add --desktop:
python3 src/codex_import.py import --all --desktop
The CLI finds transcripts by scanning ~/.claude/projects/. The desktop app doesn't —
it lists sessions from its own registry of per-session cards, joined to the transcript
by a cliSessionId field. Without a card there is no sidebar row, however valid the
transcript is. --desktop writes that card. Quit and reopen Claude Desktop
afterwards — it caches the session list in memory.
Sessions are titled after the first thing you actually typed. Codex's
<recommended_plugins> catalogue, AGENTS.md block and <environment_context> are
stripped from the title and, in the transcript, are marked isMeta: true so they
render as background rather than as chat bubbles attributed to you.
Codex Desktop's per-thread scratch dirs (~/Documents/Codex/<date>/<slug>) collapse
into one Codex sidebar folder instead of one folder each. Threads whose cwd is a
real project land in that project's folder beside your native Claude sessions.
Re-running is safe: imports are recorded in imported.json and skipped next time.
To undo everything it created:
python3 src/codex_import.py uninstall --dry-run # see what would go
python3 src/codex_import.py uninstall
Nothing is read from Codex except ~/.codex/sessions/ and the import ledger, and
nothing existing is overwritten — every import gets a fresh session UUID.
--mode archive (default) — tool calls and outputs are flattened into readable
text blocks. The transcript reads like a log. No foreign tool names ever reach the
API. Use this if you mainly want the history searchable and readable.
--mode faithful — real tool_use/tool_result pairs, so the session renders
in Claude Code the way a native one does, with collapsible tool calls. Codex exec
/ shell become Bash; MCP tools keep their mcp__* names; anything else is
prefixed codex__ so it is visibly foreign rather than impersonating a real tool.
Tested and working on resume, but it does replay tool names that don't exist in your
current session.
Both modes were verified by actually resuming an imported session and asking it to recall the Codex-side history. See FINDINGS.md.
--all / --session <prefix> | what to import |
--mode archive|faithful | see above |
--desktop | also register with the Claude desktop app so it shows in the sidebar |
--force | re-import sessions already in the ledger |
--title-prefix '[codex] ' | mark imported titles (default: unmarked) |
--no-group-scratch | keep Codex scratch dirs as separate sidebar folders |
--no-banner | skip the visible provenance banner (metadata still recorded) |
--dry-run | print the plan, write nothing |
--keep-developer | keep Codex developer/system injections as meta messages |
--include-round-trips | also import threads Codex imported from Claude (skipped by default) |
--codex-dir / --claude-dir | override source and destination roots |
Every import is recorded in three places, so provenance survives losing any one of them:
isMeta) banner naming the
Codex thread id, its start time, the source rollout path and the import mode — visible
when you open the session, without looking like something you typed. The trailing
record carries the machine-readable version: thread id, rollout path, a sha256 of the
source, import time, mode, tool version. Both travel with the file.~/.claude/codex-imports.json. The ledger, deliberately stored beside the
Claude data rather than in this checkout — deleting or moving the tool must not orphan
the imports. Keyed by Codex thread id, so a re-run skips what it already did.codexImport object. Best effort: the app owns that file
and may drop unknown keys when it rewrites the card, which is why it is not the only copy.--no-banner suppresses the visible banner; the metadata is still written.
python3 src/codex_import.py status
lists each import, both ids, and checks that all three sides still line up — flagging a missing transcript, a missing card, a deleted source rollout, or a rollout that has changed since it was imported (Codex appends to a thread when you continue it, so a changed sha means there is newer history not yet brought across).
The desktop app writes its in-memory session list back to disk, so a card removed while the app is running can reappear without its transcript. The result is a sidebar row that fails to open.
python3 src/codex_import.py repair
clears rows whose transcript is gone and reports any import that lost its transcript
(re-import those with import --all --force). Quit the app first, or the same thing can
happen again.
uninstall removes the transcripts and desktop cards this tool created, and nothing
else, using imported.json. If that ledger is lost, --scan finds them anyway —
every transcript is stamped with an import marker. --orphan-cards additionally
clears dead sidebar rows whose transcript no longer exists.
python3 tests/test_convert.py
20 fixture tests pinning the parts that were expensive to work out: which Codex records carry content, how machine preambles are split from what the user typed, the tool_use/tool_result invariant, and the two path rules.
Reasoning, mostly. Codex keeps it in an encrypted blob with a usually-empty
plaintext summary — about 85% of reasoning records have nothing recoverable, and the
rest become plain text. It can't become a Claude thinking block: those carry an
Anthropic-issued signature that's verified on replay and can't be minted.
Also dropped: token accounting, rate-limit snapshots, sandbox/permission profiles, web-search result cards, compaction history.
Everything else — user turns, assistant turns, tool calls, tool output, timestamps, cwd — comes across.
src/codex_import.py the converter
src/desktop_register.py writes the desktop app's session cards
schema/skel.py JSONL structure dumper used to do the diff
schema/*.skel.txt the dumps both formats were read from
FINDINGS.md the schema diff and how the mapping was derived
2 commits
Python
100.0%
Import OpenAI Codex conversation history into Claude Code — transcripts, not just config.
Python
0
2 commits
updated Sep 18, 2026
Import OpenAI Codex conversation history into Claude Code, so old Codex threads
show up in /resume and can be continued.
Codex can import Claude sessions. Claude Code's own /import only moves config
(AGENTS.md, subagents, prompts, MCP servers) — it has no path for transcripts.
This closes that gap.
python3 src/codex_import.py list # what's importable
python3 src/codex_import.py import --all --dry-run # see what would happen
python3 src/codex_import.py import --all # do it
python3 src/codex_import.py import --session 01a0aa55 # just one (id prefix)
That covers the CLI: claude in the session's cwd, then /resume.
For the desktop app, add --desktop:
python3 src/codex_import.py import --all --desktop
The CLI finds transcripts by scanning ~/.claude/projects/. The desktop app doesn't —
it lists sessions from its own registry of per-session cards, joined to the transcript
by a cliSessionId field. Without a card there is no sidebar row, however valid the
transcript is. --desktop writes that card. Quit and reopen Claude Desktop
afterwards — it caches the session list in memory.
Sessions are titled after the first thing you actually typed. Codex's
<recommended_plugins> catalogue, AGENTS.md block and <environment_context> are
stripped from the title and, in the transcript, are marked isMeta: true so they
render as background rather than as chat bubbles attributed to you.
Codex Desktop's per-thread scratch dirs (~/Documents/Codex/<date>/<slug>) collapse
into one Codex sidebar folder instead of one folder each. Threads whose cwd is a
real project land in that project's folder beside your native Claude sessions.
Re-running is safe: imports are recorded in imported.json and skipped next time.
To undo everything it created:
python3 src/codex_import.py uninstall --dry-run # see what would go
python3 src/codex_import.py uninstall
Nothing is read from Codex except ~/.codex/sessions/ and the import ledger, and
nothing existing is overwritten — every import gets a fresh session UUID.
--mode archive (default) — tool calls and outputs are flattened into readable
text blocks. The transcript reads like a log. No foreign tool names ever reach the
API. Use this if you mainly want the history searchable and readable.
--mode faithful — real tool_use/tool_result pairs, so the session renders
in Claude Code the way a native one does, with collapsible tool calls. Codex exec
/ shell become Bash; MCP tools keep their mcp__* names; anything else is
prefixed codex__ so it is visibly foreign rather than impersonating a real tool.
Tested and working on resume, but it does replay tool names that don't exist in your
current session.
Both modes were verified by actually resuming an imported session and asking it to recall the Codex-side history. See FINDINGS.md.
--all / --session <prefix> | what to import |
--mode archive|faithful | see above |
--desktop | also register with the Claude desktop app so it shows in the sidebar |
--force | re-import sessions already in the ledger |
--title-prefix '[codex] ' | mark imported titles (default: unmarked) |
--no-group-scratch | keep Codex scratch dirs as separate sidebar folders |
--no-banner | skip the visible provenance banner (metadata still recorded) |
--dry-run | print the plan, write nothing |
--keep-developer | keep Codex developer/system injections as meta messages |
--include-round-trips | also import threads Codex imported from Claude (skipped by default) |
--codex-dir / --claude-dir | override source and destination roots |
Every import is recorded in three places, so provenance survives losing any one of them:
isMeta) banner naming the
Codex thread id, its start time, the source rollout path and the import mode — visible
when you open the session, without looking like something you typed. The trailing
record carries the machine-readable version: thread id, rollout path, a sha256 of the
source, import time, mode, tool version. Both travel with the file.~/.claude/codex-imports.json. The ledger, deliberately stored beside the
Claude data rather than in this checkout — deleting or moving the tool must not orphan
the imports. Keyed by Codex thread id, so a re-run skips what it already did.codexImport object. Best effort: the app owns that file
and may drop unknown keys when it rewrites the card, which is why it is not the only copy.--no-banner suppresses the visible banner; the metadata is still written.
python3 src/codex_import.py status
lists each import, both ids, and checks that all three sides still line up — flagging a missing transcript, a missing card, a deleted source rollout, or a rollout that has changed since it was imported (Codex appends to a thread when you continue it, so a changed sha means there is newer history not yet brought across).
The desktop app writes its in-memory session list back to disk, so a card removed while the app is running can reappear without its transcript. The result is a sidebar row that fails to open.
python3 src/codex_import.py repair
clears rows whose transcript is gone and reports any import that lost its transcript
(re-import those with import --all --force). Quit the app first, or the same thing can
happen again.
uninstall removes the transcripts and desktop cards this tool created, and nothing
else, using imported.json. If that ledger is lost, --scan finds them anyway —
every transcript is stamped with an import marker. --orphan-cards additionally
clears dead sidebar rows whose transcript no longer exists.
python3 tests/test_convert.py
20 fixture tests pinning the parts that were expensive to work out: which Codex records carry content, how machine preambles are split from what the user typed, the tool_use/tool_result invariant, and the two path rules.
Reasoning, mostly. Codex keeps it in an encrypted blob with a usually-empty
plaintext summary — about 85% of reasoning records have nothing recoverable, and the
rest become plain text. It can't become a Claude thinking block: those carry an
Anthropic-issued signature that's verified on replay and can't be minted.
Also dropped: token accounting, rate-limit snapshots, sandbox/permission profiles, web-search result cards, compaction history.
Everything else — user turns, assistant turns, tool calls, tool output, timestamps, cwd — comes across.
src/codex_import.py the converter
src/desktop_register.py writes the desktop app's session cards
schema/skel.py JSONL structure dumper used to do the diff
schema/*.skel.txt the dumps both formats were read from
FINDINGS.md the schema diff and how the mapping was derived
2 commits
Python
100.0%