# How Session Transfer from Claude to Codex Works Internally

> Discover how session transfer from Claude to Codex works. Learn about RPC migration, local transcript validation, JSON-RPC to Codex daemon, and ledger file retrieval.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-07-29

---

**Session transfer from Claude to Codex is a pure RPC-driven migration that validates the transcript locally, sends it to the Codex daemon via JSON-RPC, and retrieves the new thread ID from a ledger file keyed by SHA-256 hash.**

The openai/codex-plugin-cc repository implements this transfer through a CLI companion that bridges Claude’s exported conversation history into native Codex threads. This article dissects the internal mechanics using the actual source implementation.

## Core Components of the Transfer Pipeline

The architecture relies on three distinct utilities spread across the `plugins/codex/scripts/lib/` directory:

### resolveClaudeSessionPath

Located in `plugins/codex/scripts/lib/claude-session-transfer.mjs` (lines 20-44), this function acts as a security and validation gate. It expands `~` shortcuts to absolute paths, verifies the file ends with `.jsonl`, and guarantees the transcript resides within the Claude projects directory (`~/.claude/projects`). If any check fails, it throws a descriptive error before network activity begins.

### importExternalAgentSession

Defined in `plugins/codex/scripts/lib/codex.mjs` (lines 58-78), this function orchestrates the RPC communication. It wraps the operation in `withDirectAppServer`, which establishes a direct connection to the Codex app-server via `CodexAppServerClient.connect`. The function then dispatches the migration request and installs a temporary notification handler that resolves a `Promise` upon receiving the `EXTERNAL_AGENT_IMPORT_COMPLETED` event. A configurable `EXTERNAL_AGENT_IMPORT_TIMEOUT_MS` guards against hanging imports.

### importedThreadIdForSource

Also in `plugins/codex/scripts/lib/codex.mjs` (lines 61-78), this utility performs the post-import lookup. It reads the ledger file at `$CODEX_HOME/external_agent_session_imports.json` and matches records where both the `source_path` and `content_sha256` fields correspond to the supplied transcript. This dual-key approach ensures deterministic thread ID retrieval.

## Step-by-Step Execution Flow

When a user invokes the transfer command, the following sequence executes:

1. **CLI Entry Point** – `handleTransfer` in `plugins/codex/scripts/codex-companion.mjs` (lines 26-33) parses arguments and delegates to `executeTransfer`.

2. **Path Resolution** – `resolveClaudeSessionPath(cwd, {source})` converts relative paths to absolute, validates the `.jsonl` extension, and confirms the file lives under `~/.claude/projects`.

3. **Server Connection** – `importExternalAgentSession` establishes a direct RPC channel to the Codex daemon.

4. **Payload Construction** – `externalAgentSessionMigration(sourcePath, cwd)` (lines 81-99 of `codex.mjs`) builds a JSON-RPC-compatible payload containing a `migrationItems` array with a single entry of type `"SESSIONS"` and the absolute source path.

5. **RPC Dispatch** – The client sends `client.request("externalAgentConfig/import", params)` and awaits the `EXTERNAL_AGENT_IMPORT_COMPLETED` notification (handled by `requestExternalAgentSessionImport` at lines 70-93).

6. **Thread ID Lookup** – Upon completion, `importedThreadIdForSource(sourcePath)` queries the ledger to retrieve the `imported_thread_id` associated with the file’s SHA-256 hash.

7. **Result Rendering** – `executeTransfer` returns an object containing `threadId`, a resume command (`codex resume <threadId>`), and the source path. `renderTransferResult` formats this for stdout.

## CLI Usage Example

Transfer a Claude session using the companion script:

```bash
node plugins/codex/scripts/codex-companion.mjs transfer --source ~/my-project/session.jsonl

```

Example output:

```text
Transferred the Claude session into a Codex thread with visible turn history.
Codex session ID: 7e2c9f93-a1b4-4c12-b8f7-d3e5a6b9c0d1
Resume in Codex: codex resume 7e2c9f93-a1b4-4c12-b8f7-d3e5a6b9c0d1

```

Under the hood, the CLI expands `~/my-project/session.jsonl` to an absolute path, validates it, opens the RPC connection, and performs the migration.

## Summary

- **Validation-First Architecture**: `resolveClaudeSessionPath` ensures only valid `.jsonl` files from the Claude projects directory proceed to import.
- **RPC-Driven Migration**: The `externalAgentConfig/import` method and `EXTERNAL_AGENT_IMPORT_COMPLETED` notification provide a synchronous handshake with the Codex daemon.
- **Deterministic Ledger**: The import ledger at `$CODEX_HOME/external_agent_session_imports.json` uses SHA-256 content hashing to map transcripts to thread IDs, making repeated transfers idempotent.
- **Timeout Protection**: `EXTERNAL_AGENT_IMPORT_TIMEOUT_MS` prevents the CLI from hanging if the daemon fails to respond.

## Frequently Asked Questions

### What file format is required for the Claude session?

The transfer requires a `*.jsonl` (JSON Lines) file containing the Claude conversation history. The `resolveClaudeSessionPath` function in `claude-session-transfer.mjs` explicitly validates this extension and rejects other formats before any network activity occurs.

### Where must the Claude transcript be located?

For security validation, the transcript must reside within the Claude projects directory at `~/.claude/projects`. The path resolution logic expands `~` shortcuts and verifies the absolute path falls within this directory tree, preventing arbitrary file access.

### How does the system handle duplicate transfers?

The ledger file (`$CODEX_HOME/external_agent_session_imports.json`) keys each import by both the absolute file path and the SHA-256 hash of the file contents. When `importedThreadIdForSource` queries the ledger, it matches on both criteria, ensuring that importing the same transcript multiple times returns the existing thread ID rather than creating a duplicate.

### What happens if the Codex daemon is unresponsive?

The `importExternalAgentSession` function implements a timeout mechanism using `EXTERNAL_AGENT_IMPORT_TIMEOUT_MS`. If the daemon fails to emit the `EXTERNAL_AGENT_IMPORT_COMPLETED` notification within this window, the Promise rejects with a timeout error, and the CLI exits gracefully rather than hanging indefinitely.