# How `importExternalAgentSession` Works for Session Transfer in OpenAI Codex Plugin

> Learn how importExternalAgentSession works for session transfer in the OpenAI Codex plugin. This function validates Claude Code transcript paths and converts them into resumable Codex threads.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-08-01

---

**`importExternalAgentSession` validates and resolves Claude Code transcript paths before converting them into resumable Codex threads.**

Session transfer in the OpenAI Codex plugin relies on a dedicated helper that safely imports external agent transcripts. The `resolveClaudeSessionPath` function in `plugins/codex/scripts/lib/claude-session-transfer.mjs` serves as the core implementation for this `importExternalAgentSession` workflow, invoked by the `codex transfer` CLI command defined in [`plugins/codex/commands/transfer.md`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/commands/transfer.md).

## Source Determination: Flags vs. Environment

The function first establishes where to find the Claude transcript. It checks for a `--source <path>` CLI flag; if absent, it falls back to the `CODEX_COMPANION_TRANSCRIPT_PATH` environment variable. If neither is provided, the function throws an error immediately.

This dual-source approach gives users flexibility: one-off transfers use explicit flags, while repeated workflows benefit from shell-level configuration.

## Path Resolution and Normalization

Once a source is identified, the function handles platform-specific path conventions:

- **Tilde expansion**: Converts `~` and `~/...` to absolute home-directory paths via `resolveUserPath` (lines 10–18)
- **Absolute path enforcement**: Calls `ensureAbsolutePath` from `plugins/codex/scripts/lib/fs.mjs` to eliminate relative ambiguity

```javascript
// From claude-session-transfer.mjs, lines 10-18
function resolveUserPath(inputPath) {
  if (inputPath.startsWith('~/')) {
    return path.join(os.homedir(), inputPath.slice(2));
  }
  return inputPath;
}

```

## File Type Validation

The function enforces strict format compliance. Using `path.extname`, it verifies the target is a `.jsonl` file—the structured JSON Lines format Claude uses for transcript storage. Non-compliant paths are rejected before any filesystem operations occur.

## Security Sandboxing via Canonicalization

Two `fs.realpathSync` calls establish a trusted boundary:

1. Resolves the transcript file to its canonical absolute path
2. Resolves Claude's projects directory at `~/.claude/projects`

```javascript
// From claude-session-transfer.mjs, lines 34-36
const realSource = fs.realpathSync(source);
const realProjectsDir = fs.realpathSync(
  path.join(os.homedir(), '.claude', 'projects')
);

```

The function then computes the relative path between these canonical locations. If the transcript lies outside `~/.claude/projects` or *is* the directory itself, it throws a security error. This prevents path-traversal attacks and confines imports to Claude's official project storage.

```javascript
// From claude-session-transfer.mjs, lines 39-42
const relative = path.relative(realProjectsDir, realSource);
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
  throw new Error('Transcript must be inside ~/.claude/projects');
}

```

## Integration with Session Transfer Pipeline

Upon successful validation, `resolveClaudeSessionPath` returns the canonical absolute path. This value is consumed by `codex-companion.mjs`, the CLI entry point for the `transfer` sub-command. The companion script:

1. Reads the `.jsonl` transcript
2. Creates a resumable Codex thread record
3. Outputs a `codex resume <session-id>` command for later use

```bash

# Explicit source flag

codex transfer --source ~/Claude/projects/my-app/session.jsonl

# Environment variable approach

export CODEX_COMPANION_TRANSCRIPT_PATH=~/Claude/projects/my-app/session.jsonl
codex transfer

```

Both invocations execute equivalent logic:

```javascript
import { resolveClaudeSessionPath } from "./lib/claude-session-transfer.mjs";

const transcriptPath = resolveClaudeSessionPath(process.cwd(), { source: cliSource });
const transcript = JSON.parse(fs.readFileSync(transcriptPath, "utf8"));
const sessionId = await createResumableCodexThread(transcript);
console.log(`codex resume ${sessionId}`);

```

## Key Design Benefits

- **Deterministic identification**: Symbolic links and relative notation resolve to consistent paths
- **Minimal attack surface**: Directory-confinement rule blocks arbitrary file imports
- **Zero-copy workflow**: Transcripts remain in place; only metadata crosses into Codex

## Summary

- The `importExternalAgentSession` workflow centers on `resolveClaudeSessionPath` in `plugins/codex/scripts/lib/claude-session-transfer.mjs`
- Source resolution prefers `--source` flags, then `CODEX_COMPANION_TRANSCRIPT_PATH` environment variable
- Path normalization handles tilde expansion and enforces absolute paths
- `.jsonl` file type validation ensures format compatibility with Claude transcripts
- Security sandboxing via `fs.realpathSync` and `path.relative` restricts imports to `~/.claude/projects`
- Validated paths feed into `codex-companion.mjs` to create resumable Codex threads

## Frequently Asked Questions

### What file format does `importExternalAgentSession` require?

The function strictly requires `.jsonl` (JSON Lines) files—the format Claude uses for transcript storage. This is enforced via `path.extname` validation before any content processing occurs.

### Why does the function reject transcripts outside `~/.claude/projects`?

The `path.relative` security check prevents path-traversal attacks and ensures only legitimate Claude project files can be imported. This sandboxing protects against accidental or malicious inclusion of unrelated files from the filesystem.

### Can I transfer a session without specifying `--source` every time?

Yes. Set the `CODEX_COMPANION_TRANSCRIPT_PATH` environment variable to your default transcript location. The function checks this variable when no `--source` flag is provided, enabling streamlined repeated transfers.

### What happens if the transcript path contains symlinks?

`fs.realpathSync` canonicalizes all paths, resolving symbolic links to their actual locations before the security boundary check. This ensures the sandbox validation operates on true filesystem locations, not potentially misleading symlink paths.