External Agent Session Import and Error Handling in the Codex Plugin
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.
// 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.
// 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.realpathSyncto resolve the absolute path, throwingError: 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 throwsError: 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:
- The
setTimeoutis always cleared viaclearTimeout(timeout). - The original
client.notificationHandleris 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:
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
.jsonlextension, and reside within~/.claude/projectsbefore the RPC is attempted. - Timeout safety: A configurable timeout prevents indefinite hangs when the server fails to signal completion.
- Guaranteed cleanup: The
try…finallypattern 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →