# External Agent Session Import and Error Handling in the Codex Plugin

> Learn the external agent session import flow and error handling in the Codex plugin. Understand the two-step RPC workflow for file path validation, payload construction, and server completion with timeouts.

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

---

**The Codex plugin imports external agent sessions through a two‑step RPC workflow that validates file paths, constructs a migration payload, and awaits server completion with timeout safeguards.**

The openai/codex-plugin-cc repository implements a secure handshake for migrating external AI agent sessions—such as Claude—into Codex. Understanding the external agent session import flow reveals how the plugin validates source files, communicates with the Codex server, and guarantees resource cleanup even when errors occur.

## How the Import Flow Works

The implementation centers on two core functions in `plugins/codex/scripts/lib/codex.mjs`: `externalAgentSessionMigration` prepares the data structure, while `requestExternalAgentSessionImport` manages the RPC lifecycle and completion detection.

### Step 1: Building the Migration Payload

The `externalAgentSessionMigration` function constructs a JSON‑compatible migration request that specifies the source session file and working directory context. This payload conforms to the schema expected by the Codex server's `"externalAgentConfig/import"` endpoint.

```typescript
// plugins/codex/scripts/lib/codex.mjs#L681-L698
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 function returns an object containing `migrationItems`, where each item declares `itemType: "SESSIONS"` and embeds the absolute path of the Claude JSONL file within the `details.sessions` array.

### Step 2: Executing the Import Request

The `requestExternalAgentSessionImport` function handles the actual RPC communication. It swaps the client's notification handler temporarily, initiates the `"externalAgentConfig/import"` request, and races the server's completion signal against a configurable timeout.

```typescript
// plugins/codex/scripts/lib/codex.mjs#L701-L730
async function requestExternalAgentSessionImport(client, params) {
  const previousHandler = client.notificationHandler;
  let timeout = null;
  let resolveCompleted, rejectCompleted;
  const completed = new Promise((resolve, reject) => {
    resolveCompleted = resolve;
    rejectCompleted = reject;
  });
  void completed.catch(() => {});

  client.setNotificationHandler((message) => {
    if (message.method === EXTERNAL_AGENT_IMPORT_COMPLETED) {
      resolveCompleted();
      return;
    }
    previousHandler?.(message);
  });

  timeout = setTimeout(() => {
    rejectCompleted(new Error("Timed out waiting for Codex to finish importing the Claude session."));
  }, EXTERNAL_AGENT_IMPORT_TIMEOUT_MS);

  try {
    await client.request("externalAgentConfig/import", params);
    await completed;
  } finally {
    clearTimeout(timeout);
    client.setNotificationHandler(previousHandler ?? null);
  }
}

```

The function preserves the original notification handler in `previousHandler`, installs a temporary handler that listens for `EXTERNAL_AGENT_IMPORT_COMPLETED`, and ensures both the timeout and handler are reset in a `finally` block.

## Error Handling Mechanisms

The plugin implements multiple layers of defense to catch invalid inputs, filesystem errors, and RPC failures before they corrupt client state.

### Pre‑flight Path Validation

Before any network request, `resolveClaudeSessionPath` (located in `plugins/codex/scripts/lib/claude-session-transfer.mjs`) enforces strict constraints on the source file:

- **Existence check**: Uses `fs.realpathSync` to resolve the absolute path, throwing `Error: Claude session file not found: <path>` if the file is missing.
- **Extension validation**: Verifies the path ends with `.jsonl`, rejecting non‑JSONL files with a descriptive error.
- **Directory containment**: Ensures the resolved path resides under `~/.claude/projects`, preventing path traversal attacks. If the file lies outside this directory, the function throws `Error: Codex can import Claude sessions only from <dir>: <source>`.

### RPC Timeout Protection

If the Codex server fails to emit the `EXTERNAL_AGENT_IMPORT_COMPLETED` notification within `EXTERNAL_AGENT_IMPORT_TIMEOUT_MS` milliseconds, the timeout handler rejects the completion promise with:

```

Error: Timed out waiting for Codex to finish importing the Claude session.

```

This prevents the client from hanging indefinitely on unresponsive servers.

### Resource Cleanup Guarantees

The `try…finally` block in `requestExternalAgentSessionImport` ensures that:

1. The `setTimeout` is always cleared via `clearTimeout(timeout)`.
2. The original `client.notificationHandler` is restored, even if the RPC throws or the promise rejects.

This pattern prevents memory leaks and notification handler corruption when integrating with older Codex CLI versions that may lack the `"externalAgentConfig/import"` RPC method.

## Complete Implementation Example

The following snippet demonstrates the end‑to‑end flow, including error handling:

```typescript
import { resolveClaudeSessionPath } from "./lib/claude-session-transfer.mjs";
import { externalAgentSessionMigration, requestExternalAgentSessionImport } from "./lib/codex.mjs";

try {
  // 1. Resolve and validate the Claude JSONL file
  const sourcePath = resolveClaudeSessionPath(process.cwd(), { 
    source: "/home/user/.claude/projects/my-session.jsonl" 
  });

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

  // 3. Execute the import with timeout protection
  await requestExternalAgentSessionImport(codexClient, migration);
  console.log("✅ Session imported successfully");
} catch (err) {
  console.error("Import failed:", err.message);
}

```

## Summary

- **Two‑step architecture**: The flow separates payload construction (`externalAgentSessionMigration`) from RPC execution (`requestExternalAgentSessionImport`), enabling testable, modular code.
- **Strict validation**: Files must exist, use the `.jsonl` extension, and reside within `~/.claude/projects` before the RPC is attempted.
- **Timeout safety**: A configurable timeout prevents indefinite hangs when the server fails to signal completion.
- **Guaranteed cleanup**: The `try…finally` pattern ensures notification handlers and timers are restored regardless of success or failure.
- **Graceful degradation**: Unknown notifications are forwarded to the previous handler, preserving other client functionality during the import.

## Frequently Asked Questions

### How does the Codex plugin validate external agent session files before import?

The plugin validates files through `resolveClaudeSessionPath`, which checks that the path exists via `fs.realpathSync`, enforces a `.jsonl` extension, and verifies the file resides within the `~/.claude/projects` directory to prevent directory traversal. Any violation throws a descriptive error before network communication begins.

### What happens if the Codex server never signals import completion?

If the server does not emit the `EXTERNAL_AGENT_IMPORT_COMPLETED` notification within the duration specified by `EXTERNAL_AGENT_IMPORT_TIMEOUT_MS`, the promise returned by `requestExternalAgentSessionImport` rejects with a timeout error. The temporary notification handler and timeout are still cleaned up in the `finally` block to prevent resource leaks.

### Can I import sessions from directories outside `~/.claude/projects`?

No. The `resolveClaudeSessionPath` function explicitly checks that the resolved real path is contained within the Claude projects directory. Attempting to import from outside this directory triggers an error stating that Codex can import Claude sessions only from the allowed directory.

### How does the plugin handle compatibility with older Codex CLI versions?

If the Codex CLI lacks the `"externalAgentConfig/import"` RPC method, the `await client.request()` call will throw an RPC error. This error bubbles up to the caller, while the surrounding `try…finally` ensures the original notification handler is restored and the timeout is cleared, leaving the client in a consistent state.