External Agent Session Import Mechanism and Ledger File in the Codex Plugin
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.
// 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:
// 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_COMPLETEDarrives - 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
{
"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:
// 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
nullif 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
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
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 andrequestExternalAgentSessionImport()to coordinate with the Codex app-server via theexternalAgentConfig/importmethod. - The ledger file at
~/.codex/external_agent_session_imports.jsoncaches 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 intests/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.
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 →