openai/codex-plugin-cc · error · Error

Codex can import Claude sessions only from ${CLAUDE_PROJECTS

Error message

Codex can import Claude sessions only from ${CLAUDE_PROJECTS_DIR}: ${source}

What it means

Thrown by resolveClaudeSessionPath as a path-traversal / sandbox guard. After realpathSync resolves both the source and ~/.claude/projects, it computes path.relative(projects, source); if the result is '', '..', starts with '..'+sep, or is absolute, the source lies outside the allowed projects directory and is rejected. Only transcripts physically under ~/.claude/projects may be imported.

Source

Thrown at plugins/codex/scripts/lib/claude-session-transfer.mjs:41

    throw new Error("Could not identify the current Claude transcript. Retry with --source <path-to-claude-jsonl>.");
  }

  const sourcePath = resolveUserPath(cwd, requestedPath);
  if (path.extname(sourcePath) !== ".jsonl") {
    throw new Error(`Claude session source must be a JSONL file: ${sourcePath}`);
  }

  let source;
  let projects;
  try {
    source = fs.realpathSync(sourcePath);
    projects = fs.realpathSync(CLAUDE_PROJECTS_DIR);
  } catch {
    throw new Error(`Claude session file not found: ${sourcePath}`);
  }
  const relative = path.relative(projects, source);
  if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
    throw new Error(`Codex can import Claude sessions only from ${CLAUDE_PROJECTS_DIR}: ${source}`);
  }
  return source;
}

View on GitHub (pinned to db52e28f4d)

Solutions

  1. Place (or symlink) the transcript physically inside ~/.claude/projects/<encoded-cwd>/ and pass that path.
  2. If you have a transcript elsewhere, copy it into the projects dir rather than referencing an external location.
  3. Resolve symlinks yourself and confirm path.relative(os.homedir()+'/.claude/projects', realpath) does not start with '..'.
  4. Ensure the encoded project directory name matches Claude's convention (cwd path with separators replaced).

Example fix

// before
resolveClaudeSessionPath(cwd, { source: '/tmp/exported-session.jsonl' }) // throws (outside projects)

// after
// copy into the allowed projects dir:
//   cp /tmp/exported-session.jsonl ~/.claude/projects/-tmp-myapp/session.jsonl
resolveClaudeSessionPath(cwd, { source: '~/.claude/projects/-tmp-myapp/session.jsonl' })
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';

function assertInsideProjects(p) {
  const projects = fs.realpathSync(path.join(os.homedir(), '.claude', 'projects'));
  const real = fs.realpathSync(p);
  const rel = path.relative(projects, real);
  if (rel === '' || rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
    throw new Error(`Transcript must live under ${projects}`);
  }
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Passing a source that resolves (via symlink or absolute path) outside ~/.claude/projects, e.g. /tmp/session.jsonl, /etc/something, or a symlink under projects that points elsewhere. The relative computation escapes the projects root.

Common situations: User copies a transcript to /tmp for convenience and points --source there. A symlinked transcript whose realpath target is outside projects. Attempting to import from a custom backup directory. The encoded projects subdir name does not match, so the relative path begins with '..'.

Related errors


AI-assisted analysis of openai/codex-plugin-cc@db52e28f4d (2026-08-13). Data as JSON: /api/errors/155d78064da5245c. Report an issue: GitHub.