codex-claude-transfer
Transfer your local Codex & Claude Code sessions between machines.
The command is cct.

⚠️ Unofficial. Not affiliated with or endorsed by OpenAI or Anthropic.
These tools' internals can change at any time and break this tool. Use at your
own risk — see the Disclaimer.
Status: Works with both Codex and Claude Code. Pick the agent with
--tool codex / --tool claude (auto-detected when only one is installed), and
move a session from one agent to the other with import --to codex|claude.
The Claude Code storage format and its file-based resume contract were verified
empirically against a live install (see
docs/research/claude-code-sessions-investigation.md).
cct is a small, local-only CLI that moves your
Codex and
Claude Code sessions between machines
by hand. You export a project's sessions into one .codexbundle file, copy it
across however you like (USB stick, scp, Syncthing, an encrypted drive), and
import it on the other machine. No cloud, no account, no server, no daemon —
and the agent's index/state is never touched.
Machine A: cct export --project . → project.codexbundle
⇣ (copy the file across yourself)
Machine B: cct import ./project.codexbundle
# Claude Code instead of Codex:
Machine A: cct export --tool claude --project .
Machine B: cct import ./project.codexbundle # the bundle knows it's Claude
How it works
Each agent stores a session as a durable JSONL file, with a rebuildable index
alongside it that cct never writes:
- Codex →
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl (its SQLite DB is the
index).
- Claude Code →
~/.claude/projects/<encoded-cwd>/<uuid>.jsonl (its
~/.claude.json holds only config, not a session index).
cct works only with those JSONL files: export packages them (with a manifest
and SHA-256 checksums) into a .codexbundle ZIP, and import copies them back
into place after verifying every checksum. It never writes the index — each agent
re-discovers the files itself on its next run, so imported sessions show up the
next time you start it. The bundle records which agent it came from, so import
always writes to the right home.
Install
# From source (Go 1.23+)
go install github.com/ahmojo/codex-claude-transfer/cmd/cct@latest
Or download a prebuilt binary from Releases,
or build from a clone:
git clone https://github.com/ahmojo/codex-claude-transfer.git
cd codex-claude-transfer && go build -o cct ./cmd/cct
Package manifests for Homebrew and Scoop live in
packaging/ (they install the prebuilt release binary).
The binary is self-contained. Only the interactive ui command uses a
third-party library (charmbracelet/huh);
the core packages are standard-library only.
The core commands need nothing extra. A few opt-in features shell out to a
standard tool if you use them; without it, that feature errors with guidance
or is simply skipped — nothing else is affected.
| Tool |
Enables |
Without it |
git |
export --with-git, import --clone |
git metadata not recorded; --clone errors |
age |
bundle encryption / decryption |
encrypt/decrypt errors; plain bundles unaffected |
zstd |
reading compressed .jsonl.zst metadata; --map-cwd on compressed sessions |
compressed sessions are copied as-is, with cwd/preview unknown |
These tools are only ever used to read (clone/fetch, decompress) or to
encrypt/decrypt locally — they never change the "nothing is uploaded" guarantee.
Quickstart
cct is a CLI first — the commands below are the whole tool. Two optional
front-ends are included if you prefer not to type flags: cct app (a
graphical app in your browser) and cct ui (a guided terminal menu).
Neither is required; everything they do is just the flags.
cct doctor # check it can see your sessions
cct list # list discovered sessions
cct export --project . # → project.codexbundle
# … copy the bundle to the other machine …
cct inspect ./project.codexbundle # look inside (read-only)
cct import ./project.codexbundle --dry-run # preview, write nothing
cct import ./project.codexbundle # import for real
# For Claude Code, add --tool claude to doctor/list/export
# (import auto-detects the agent from the bundle):
cct list --tool claude
cct export --tool claude --project .
After importing, run the agent again (restart Codex, or relaunch Claude Code)
so it re-scans the files.
Desktop app (optional)
If you'd rather click than type, cct app gives you a small graphical
interface with Doctor, Sessions, Export, Inspect, and Import views — the same
operations as the CLI, with project folders and sessions shown in lists and every
import previewed before anything is written.
cct app # opens the app in your default browser
cct app --no-browser # just print the URL (open it yourself)
cct app --port 8765 # pin a port (default: a free one is chosen)
How it works. It is not a separate program or an Electron-style native
window — it is the same cct binary serving a tiny web page to your own
browser. On launch it starts a small web server bound to 127.0.0.1 only
(your machine, not the network), prints a URL, and opens it. The page talks to the
local server, which runs the exact same export/import code as the CLI. It is
optional and self-contained: no extra install, no Node, no toolchain — just
the one binary.
Why it's safe. The server is reachable only from your own machine; every
action requires a random token generated fresh each launch (handed to your browser
through the launch URL, never embedded in the page), and requests with a foreign
Host header are refused. It never uploads anything — the browser is just the
UI over your local files. Stop it with Ctrl-C when you're done. See
docs/safety.md for the details.
Common workflows
The project is at a different path on the other machine. Codex's per-project
sidebar filters by the session's recorded working directory, so remap it on
import (preview with --dry-run first):
cct import ./project.codexbundle \
--map-cwd "/Users/me/dev/project=C:\\Users\\me\\dev\\project"
inspect and import flag any recorded folder that's missing locally and print a
ready-to-paste mapping.
Bring the code too (git handoff). --with-git records the project's
remote/branch/commit; --clone checks it out on the other side. If your latest
commit isn't pushed yet, add --git-push to push your branch to its own git
remote first, so the recorded commit is actually fetchable — it uploads your
code to your own remote only, never your sessions. Without --clone, import
just prints the git clone … && git checkout <commit> commands for you.
cct export --project . --with-git --git-push
cct import ./project.codexbundle --clone ~/dev/project
Encrypt a bundle in transit (via age).
--encrypt-to <recipient> (or --passphrase) writes <output>.age and removes
the plaintext; import/inspect auto-detect and decrypt a .age bundle.
cct export --project . --encrypt-to age1qz...
cct import ./project.codexbundle.age --identity ~/.age/key.txt
Just one session, or a subset. export --session <id> exports a single
conversation (a unique prefix is enough); import --session <id> (repeatable)
imports only the chosen ones.
Resolve a diverged session. By default, a local session that differs from the
bundle is reported as a conflict and skipped. Opt into --replace-with-backup
(overwrite, keeping a backup) or --import-as-copy (import the bundle's version
as a brand-new session, leaving yours untouched).
Move work between agents (cross-agent handoff). import --to <agent> doesn't
import the bundle natively — it translates each session into the other
agent's format and writes a real, discoverable session into that agent's home:
cct export --project . # a Codex bundle
cct import ./project.codexbundle --to claude # continue it in Claude Code
cct export --tool claude --project . # a Claude Code bundle
cct import ./project.codexbundle --to codex # continue it in Codex
The translated session opens with a short handoff note (project dir, git, "continue
from here") and replays the prior conversation as text. It's an honest
best-effort handoff, not a perfect clone: the conversation and project context
cross over, but tool calls and command output are summarized rather than replayed,
and model/runtime state does not transfer. It's deterministic, so re-running is an
idempotent skip rather than a duplicate.
Command reference
| Command |
Description |
cct app |
Launch the desktop GUI: a loopback-only local web app that opens in your browser. Nothing is uploaded. |
cct ui |
Interactive guided menu; builds and runs the commands below (and prints each one). Requires a terminal. |
cct doctor |
Read-only health check: the agent's home, session counts, missing-cwd and optional-tool (git/age/zstd) status. Use --tool to pick Codex or Claude Code. |
cct list |
List discovered sessions (preview, thread id, cwd, source, updated time). |
cct export [--project <path> | --all | --session <id>] |
Package matching sessions into a .codexbundle. |
cct inspect <bundle> |
Show a bundle's manifest and contents, read-only, and flag any recorded project folder that's missing locally. |
cct import <bundle> |
Import session files into the matching agent's home (or translate across agents with --to). Verifies checksums; never overwrites by default. |
cct version |
Print the version (also --version). |
cct completion <bash|zsh|fish> |
Print a shell completion script. |
cct help |
Show help. |
Flags
| Flag |
Applies to |
Meaning |
--tool <codex|claude> |
all |
Which agent to act on. Default: auto-detect (Claude Code if only it is installed, else Codex). On import the bundle's recorded tool always wins. |
--codex-home <path> |
all |
Use a specific Codex home instead of the default (also honors $CODEX_HOME). |
--claude-home <path> |
all |
Use a specific Claude Code home instead of ~/.claude (also honors $CLAUDE_HOME). |
--project <path> |
export, import |
Export: filter sessions by recorded cwd. Import: warn on cwd mismatch. |
--all |
export |
Export every session regardless of cwd. Mutually exclusive with --project. |
--session <id> |
export, import |
Export: exactly one session by thread id (unique prefix); excludes --all/--project. Import: only the matching session(s); repeatable. |
--since <when> |
export |
Only sessions updated at/after a date (YYYY-MM-DD) or duration (7d, 48h, 90m). |
--with-git |
export |
Also record the project's git remote/branch/commit (and dirty/unpushed status). |
--git-push |
export |
Opt-in. Push the project's current branch to its own git remote first, so the recorded commit is fetchable on the other machine. Uploads your code only, never sessions; never force-pushes. Needs a project and a remote. |
--output, -o <path> |
export |
Bundle output path (defaults derived from --project/--all/--session). |
--include-archived |
list, export |
Also consider archived sessions. |
--json |
doctor, list, inspect, export, import |
Print a machine-readable JSON summary on stdout instead of text. |
--dry-run |
import |
Validate and report only; write nothing. |
--to <codex|claude> |
import |
Cross-agent handoff: translate the bundle's sessions into the other agent's format and write them into that agent's home (best-effort: conversation + context preamble, tool calls summarized). |
--map-cwd OLD=NEW |
import |
Rewrite matching sessions' recorded cwd. Plain .jsonl always; .jsonl.zst when zstd is installed. Repeatable. |
--replace-with-backup |
import |
On a conflict, back up the local file and overwrite it with the bundle's version. |
--import-as-copy |
import |
On a conflict, import the bundle's version as a new session, leaving yours untouched. Excludes --replace-with-backup. |
--clone <dir> |
import |
After importing, clone the bundle's recorded git remote into <dir> and check out its commit. |
--encrypt-to <recipient> |
export |
Encrypt to an age recipient (age1.../ssh-ed25519 ...); repeatable. Writes <output>.age. |
--recipients-file <file> |
export |
Encrypt to every age recipient listed in <file>. |
--passphrase |
export, import, inspect |
Export: encrypt with a passphrase. Import/inspect: decrypt a passphrase-encrypted bundle. |
--identity <file> |
import, inspect |
age identity (private key) file used to decrypt a .age bundle. |
Safety
Safe by default — the full model and privacy notes are in
docs/safety.md. In short:
- Checksums are verified before any write; a corrupt or tampered bundle
changes nothing.
- No silent overwrites: new files are written, identical ones skipped, and a
differing one is reported as a conflict and skipped — unless you opt into
--replace-with-backup or --import-as-copy.
- SQLite is never modified; path-traversal/zip-slip and absolute paths are
rejected; writes are atomic (temp file + rename).
- Default import is byte-for-byte. The only content changes are opt-in and
narrow:
--map-cwd (the cwd field) and --import-as-copy (the id field),
each validated before writing.
A .codexbundle can contain prompts, code, command output, file paths, and
accidentally-printed secrets. Treat it like your shell history plus your
source tree: don't post it publicly, and encrypt it to move over a channel you
don't fully control.
A .codexbundle is a ZIP archive:
project.codexbundle
├── manifest.json # format version, source info, per-session metadata
├── checksums.json # SHA-256 of every other file (not itself)
└── sessions/YYYY/MM/DD/rollout-…-<uuid>.jsonl[.zst]
Format version codex-sync-bundle-v1. Compressed .jsonl.zst rollouts are copied
in byte-for-byte and never recompressed or modified; their metadata may be
read (decompressed) on export when zstd is installed.
Limitations
- Codex internals may change. Parsing is defensive, but the on-disk format can
drift — re-check after Codex updates.
- Compressed
.jsonl.zst sessions need zstd to recover their metadata and to
be remapped with --map-cwd; without it they're copied as-is, and their cwd is
unknown to the --project filter (use --all to include them).
- Project visibility depends on matching cwd paths. If the project lives at a
different path on each machine, an imported session may not appear under that
project until you
--map-cwd it.
- No global path rewriting, no merge, no cloud sync.
--map-cwd only changes
the cwd field in session_meta; sessions are copied, not merged.
- The desktop GUI runs in your browser, not a native window —
cct app
serves a local, loopback-only web app (no native packaging, no extra toolchain).
- Claude Code's format is closed-source and moves fast. Support was verified
against a recent install and parses defensively, but re-check after Claude Code
updates. Sub-agent sidechains are carried inside the same transcript file (and
whole project folders travel together on export); a logical session that spans
separate sidechain files has not been observed and is not specially handled yet.
- Cross-agent handoff (
--to) is a translation, not a clone. It carries the
conversation and project context and writes a session the target agent can
discover, but tool calls/command output are summarized (not replayed), and
model/runtime state, exact tool-call history, and provider-specific ids do not
transfer. Treat a handed-off session as a primed "continue from here" context,
not a byte-for-byte continuation.
Roadmap
Shipped since v0.1.0: --map-cwd, export --all/--since/--session/--with-git,
import --clone, age encryption, cwd discovery, --replace-with-backup, an
interactive ui, --import-as-copy, zstd-based compressed-session support,
doctor tool checks, --json output, selective import --session,
version/completion commands, opt-in export --git-push, a desktop GUI
(cct app, a loopback-only local web app over the same Go core),
Claude Code support (--tool claude) across every command and both
front-ends, and cross-agent handoff (import --to codex|claude) that
translates a session from one agent into the other.
Never planned: cloud sync, accounts, hosting, background sync, direct
index/SQLite writes, global path rewriting, automatic merge, or uploading your
sessions anywhere.
Built with AI assistance
Largely implemented with Claude Opus 4.8 under the maintainer's direction (design,
safety constraints, source investigation, review, and releases). Treat it like any
other open-source code: review it, test it, and report issues — the AI is a
tool, not a guarantee of correctness.
Contributing
PRs welcome. Keep the no-cloud / no-SQLite-writes principles and the import path
safe (no silent overwrites), treat anything that mutates session content as
security-sensitive, test with fake Codex homes only, and run
go fmt/vet/build/test ./.... See CONTRIBUTING.md. Licensed
under MIT.
Disclaimer
Unofficial and not affiliated with or endorsed by OpenAI or Anthropic. It
works against Codex's and Claude Code's local files based on their behavior at a
point in time, which may change and break it (Claude Code is closed-source and
changes often). .codexbundle files can contain sensitive data (see
docs/safety.md). Provided "as is", without warranty.
Use at your own risk.