# External Agent Session Import Mechanism and Ledger File in the Codex Plugin

> Learn how the Codex plugin imports external agent sessions and uses a ledger file to prevent duplicates. Understand the session import mechanism and caching process.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: how-to-guide
- Published: 2026-08-04

---

**The Codex plugin imports external Claude sessions into Codex threads using `externalAgentSessionMigration()` and `requestExternalAgentSessionImport()`, then caches results in a JSON ledger file at `~/.codex/external_agent_session_imports.json` to prevent duplicate imports.**

The **external agent session import mechanism** enables seamless migration of Claude conversation sessions into the Codex environment. This functionality, implemented in `plugins/codex/scripts/lib/codex.mjs`, relies on a persistent **ledger file** to track imported sessions and avoid redundant processing. Understanding both components is essential for developers integrating external agent workflows with Codex.

## How External Agent Session Import Works

The import process follows a four-stage pipeline that transforms a source session file into a reusable Codex thread.

### 1. Building the Migration Payload

The `externalAgentSessionMigration(sourcePath, cwd)` function constructs a **migration description** that instructs Codex to create a new session from the specified file. This payload conforms to the standard `migrationItems` structure.

```javascript
// From plugins/codex/scripts/lib/codex.mjs, lines 81-98
function externalAgentSessionMigration(sourcePath, cwd) {
  return {
    type: 'external_agent_session',
    source_path: sourcePath,
    working_directory: cwd,
    // Additional migration metadata...
  };
}

```

### 2. Requesting the Import

The `requestExternalAgentSessionImport(client, params)` function handles the actual communication with the Codex app-server:

```javascript
// From plugins/codex/scripts/lib/codex.mjs, lines 101-130
async function requestExternalAgentSessionImport(client, params) {
  const originalHandler = client.handleNotification;
  
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      client.handleNotification = originalHandler;
      reject(new Error('Import timeout'));
    }, 30000);
    
    client.handleNotification = (method, params) => {
      if (method === 'EXTERNAL_AGENT_IMPORT_COMPLETED') {
        clearTimeout(timeout);
        client.handleNotification = originalHandler;
        resolve(params);
      }
    };
    
    client.sendRequest('externalAgentConfig/import', params);
  });
}

```

This function:
- Temporarily replaces the client's notification handler
- Sets a 30-second timeout for the operation
- Resolves when `EXTERNAL_AGENT_IMPORT_COMPLETED` arrives
- Rejects on timeout or error

### 3. Thread Initialization

After successful import, the plugin either starts a new thread via `startThread()` or resumes an existing one with `resumeThread()`. The import result may contain an `imported_thread_id` that links the external session to the Codex thread.

### 4. Ledger Caching

To prevent re-importing identical sessions, the plugin records the source file path, its SHA-256 hash, and the resulting thread ID in the ledger file.

## The Ledger File: Structure and Purpose

The **ledger file** serves as a deterministic cache that enables thread reuse based on exact session file content.

### Location

The ledger resides at:

```

$CODEX_HOME/external_agent_session_imports.json

```

If `CODEX_HOME` is unset, it defaults to `~/.codex`. The path is assembled by `importLedgerPath()` in both production code and test fixtures.

### File Structure

```json
{
  "records": [
    {
      "source_path": "/home/user/project/claude_session.json",
      "content_sha256": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
      "imported_thread_id": "thread_12345"
    }
  ]
}

```

Each record contains:
- **source_path**: Real-resolved absolute path of the imported file
- **content_sha256**: SHA-256 hash of the file contents
- **imported_thread_id**: Thread ID assigned by Codex after import

### Lookup Operations

The `importedThreadIdForSource(sourcePath)` function (lines 61-78) performs ledger lookups:

```javascript
// From plugins/codex/scripts/lib/codex.mjs
function importedThreadIdForSource(sourcePath) {
  const ledger = loadImportLedger();
  const normalizedPath = path.resolve(sourcePath);
  const contentHash = computeSha256(normalizedPath);
  
  const record = ledger.records
    .reverse()
    .find(r => r.source_path === normalizedPath && 
               r.content_sha256 === contentHash);
               
  return record?.imported_thread_id ?? null;
}

```

The function:
- Normalizes the source path using `path.resolve()`
- Computes the SHA-256 hash of file contents
- Returns the most recent matching `imported_thread_id`
- Returns `null` if no entry exists

### Ledger Updates

When an import succeeds, `saveImportLedger()` appends a new record and persists the file. The test harness in `tests/fake-codex-fixture.mjs` (lines 57-61) emulates this behavior for validation.

## Practical Implementation Examples

### Complete Import Workflow

```javascript
import { 
  withAppServer, 
  externalAgentSessionMigration, 
  requestExternalAgentSessionImport, 
  importedThreadIdForSource 
} from './lib/codex.mjs';

async function importClaudeSession(sourcePath, cwd) {
  const client = await CodexAppServerClient.connect(cwd);
  
  try {
    // 1. Check ledger for existing import
    const cachedThreadId = importedThreadIdForSource(sourcePath);
    if (cachedThreadId) {
      console.log('Reusing cached thread:', cachedThreadId);
      return cachedThreadId;
    }

    // 2. Build migration payload
    const migration = externalAgentSessionMigration(sourcePath, cwd);

    // 3. Request import from Codex server
    await requestExternalAgentSessionImport(client, migration);

    // 4. Start new thread for imported session
    const { thread } = await startThread(client, cwd);
    const threadId = thread.id;

    // 5. Ledger update happens automatically via plugin internals
    return threadId;
    
  } finally {
    await client.close();
  }
}

```

### Ledger-Based Thread Reuse

```javascript
import { importedThreadIdForSource } from './lib/codex.mjs';

function checkExistingImport(sessionPath) {
  const existingId = importedThreadIdForSource(sessionPath);
  
  if (existingId) {
    return {
      canReuse: true,
      threadId: existingId,
      action: 'resumeThread'
    };
  }
  
  return {
    canReuse: false,
    threadId: null,
    action: 'importAndStartThread'
  };
}

```

## Key Source Files and Functions

| Component | Location | Purpose |
|-----------|----------|---------|
| Migration builder | `plugins/codex/scripts/lib/codex.mjs:81-98` | Creates `externalAgentSessionMigration` payloads |
| Import request handler | `plugins/codex/scripts/lib/codex.mjs:101-130` | Manages `externalAgentConfig/import` RPC and notification waiting |
| Ledger lookup | `plugins/codex/scripts/lib/codex.mjs:61-78` | `importedThreadIdForSource()` function |
| Ledger path utility | `tests/fake-codex-fixture.mjs:48-50` | `importLedgerPath()` implementation |
| Ledger persistence | `tests/fake-codex-fixture.mjs:57-61` | `saveImportLedger()` and `loadImportLedger()` |
| Thread start integration | `plugins/codex/scripts/lib/codex.mjs:1080-1085` | Uses `imported_thread_id` when starting threads |

## Summary

- The **external agent session import mechanism** uses `externalAgentSessionMigration()` to prepare payloads and `requestExternalAgentSessionImport()` to coordinate with the Codex app-server via the `externalAgentConfig/import` method.
- The **ledger file** at `~/.codex/external_agent_session_imports.json` caches mappings between source session files (by path and SHA-256 hash) and their resulting Codex thread IDs.
- **Thread reuse** is determined by exact content matching—modifying a session file triggers a fresh import due to hash mismatch.
- All core functionality resides in `plugins/codex/scripts/lib/codex.mjs`, with test coverage in `tests/fake-codex-fixture.mjs`.

## Frequently Asked Questions

### What triggers a new import versus reusing an existing thread?

A new import occurs when `importedThreadIdForSource()` finds no ledger entry matching both the resolved source path and the current SHA-256 hash of the file contents. If the file changes in any way—including whitespace or encoding—the hash mismatch forces re-import.

### Where is the ledger file stored and can it be relocated?

The ledger file defaults to `~/.codex/external_agent_session_imports.json`. It respects the `CODEX_HOME` environment variable, allowing relocation by setting `CODEX_HOME` to an alternative directory before running the plugin.

### How does the plugin handle import failures or timeouts?

The `requestExternalAgentSessionImport()` function implements a 30-second timeout mechanism. If `EXTERNAL_AGENT_IMPORT_COMPLETED` does not arrive within this window, or if an error notification is received, the promise rejects and the original notification handler is restored. The ledger remains unchanged on failure, so subsequent attempts will retry the full import process.

### Can multiple external agent sessions be imported into the same Codex thread?

The current implementation creates a distinct thread per imported session. The `imported_thread_id` in the ledger represents a one-to-one mapping between source session file and Codex thread. To merge multiple sessions, you would need to combine them externally before import or manage multiple thread IDs within your application logic.