How Session Import and External Agent Transfer Work in Codex

Codex imports Claude chat sessions by validating transcript paths, constructing JSON-RPC migration payloads, and waiting for the app-server to confirm completion via a notification handler.

The openai/codex-plugin-cc repository enables seamless migration of Claude conversation history into Codex through a structured three-stage process. This session import and external agent transfer functionality converts local .jsonl transcript files into fully-fledged Codex threads while preserving the complete conversation context.

Stage 1: Locating the Claude Transcript

The process begins with path validation to ensure security and file integrity. The resolveClaudeSessionPath function in plugins/codex/scripts/lib/claude-session-transfer.mjs (lines 20‑44) accepts a user-provided path and enforces three critical constraints:

  • The file must exist and use the .jsonl extension
  • The path must reside within the ~/.claude/projects directory tree
  • The function returns an absolute path for downstream processing

If validation fails, the function raises descriptive errors indicating whether the file is missing, has an incorrect extension, or lives outside the permitted directory.

// plugins/codex/scripts/lib/claude-session-transfer.mjs
export function resolveClaudeSessionPath(cwd, options = {}) {
  // Validates path is inside ~/.claude/projects and ends with .jsonl
  // Returns absolute path or throws descriptive error
}

Stage 2: Creating the Migration Payload

Once the transcript path is verified, externalAgentSessionMigration in plugins/codex/scripts/lib/codex.mjs (lines 81‑99) constructs a JSON-RPC-compatible migration object. This payload describes the session to be transferred using the SESSIONS item type:

// plugins/codex/scripts/lib/codex.mjs
function externalAgentSessionMigration(sourcePath, cwd) {
  return {
    migrationItems: [{
      itemType: "SESSIONS",
      description: `Transfer Claude session ${path.basename(sourcePath)}`,
      cwd: null,
      details: {
        plugins: [],
        sessions: [{ path: sourcePath, cwd, title: null }],
        mcpServers: [],
        hooks: [],
        subagents: [],
        commands: []
      }
    }]
  };
}

The migration object includes the source file path, working directory context, and empty arrays for related resources like MCP servers and subagents that may be configured later.

Stage 3: Requesting the Import

The requestExternalAgentSessionImport function (lines 101‑130 in the same file) handles the actual RPC communication. It temporarily overrides the client's notification handler to listen for the externalAgentConfig/import/completed event while sending the migration payload via the externalAgentConfig/import method:

// plugins/codex/scripts/lib/codex.mjs
async function requestExternalAgentSessionImport(client, params) {
  const previousHandler = client.notificationHandler;
  // Set up temporary handler for completion notification
  client.setNotificationHandler((msg) => {
    if (msg.method === "externalAgentConfig/import/completed") {
      resolveCompleted();
      return;
    }
    previousHandler?.(msg);
  });
  
  await client.request("externalAgentConfig/import", params);
  await completed; // Resolves when server confirms import
}

Upon successful import, Codex creates a ledger entry in external_agent_session_imports.json to record the transfer for later lookups.

Running the Transfer Command

The transfer command (plugins/codex/commands/transfer.md) wires these three functions together into a user-friendly CLI workflow. You can invoke it with an explicit source path:

codex transfer --source ~/.claude/projects/my-chat.jsonl

Alternatively, set the CODEX_COMPANION_TRANSCRIPT_PATH environment variable to skip the flag:

export CODEX_COMPANION_TRANSCRIPT_PATH=~/.claude/projects/chat.jsonl
codex transfer

When the app-server supports the import capability, this creates a new Codex thread containing the complete Claude conversation history.

Programmatic Implementation

For custom integrations, import the helper functions directly and manage the app-server connection manually:

import { resolveClaudeSessionPath } from "./plugins/codex/scripts/lib/claude-session-transfer.mjs";
import { externalAgentSessionMigration, requestExternalAgentSessionImport } from "./plugins/codex/scripts/lib/codex.mjs";
import { CodexAppServerClient } from "./plugins/codex/scripts/lib/app-server.mjs";

async function importClaudeSession(cwd, source) {
  const sourcePath = resolveClaudeSessionPath(cwd, { source });
  const migration = externalAgentSessionMigration(sourcePath, cwd);
  const client = await CodexAppServerClient.connect(cwd);
  
  await requestExternalAgentSessionImport(client, migration);
  await client.close();
}

// Example usage
await importClaudeSession(process.cwd(), "~/.claude/projects/my-chat.jsonl");

This approach gives you full control over the client lifecycle and error handling while using the same validation and migration logic as the CLI.

Summary

  • Path validation occurs via resolveClaudeSessionPath in plugins/codex/scripts/lib/claude-session-transfer.mjs, which restricts imports to .jsonl files within ~/.claude/projects.
  • Payload construction uses externalAgentSessionMigration to create JSON-RPC compatible migration objects with itemType: "SESSIONS".
  • RPC execution happens through requestExternalAgentSessionImport, which waits for the externalAgentConfig/import/completed notification before resolving.
  • Ledger recording stores import history in external_agent_session_imports.json for status tracking.
  • CLI access is provided by the codex transfer command, which orchestrates the entire workflow.

Frequently Asked Questions

What file format does Codex require for session imports?

Codex strictly requires JSON Lines (.jsonl) files that contain Claude transcript data. The resolveClaudeSessionPath function enforces this extension and rejects any other file types before processing begins.

How does Codex confirm that an import succeeded?

The system uses an asynchronous notification pattern. After sending the externalAgentConfig/import RPC request, requestExternalAgentSessionImport waits for the server to emit an externalAgentConfig/import/completed notification. The function rejects the promise if this confirmation times out or if the server returns an error.

Can I import sessions from agents other than Claude?

The current implementation in openai/codex-plugin-cc specifically targets Claude transcripts stored in ~/.claude/projects. While the migration payload structure in externalAgentSessionMigration is generic enough to support other sources, the path validation logic and SESSIONS item type are currently optimized for Claude conversation files.

Where does Codex store the import history?

After a successful transfer, Codex records the operation in external_agent_session_imports.json, which functions as a ledger for tracking which sessions have been imported. This enables the codex status command to display imported sessions and prevent duplicate transfers.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →