Description
/codex:transfer cannot transfer a session when Claude Code's config dir is not ~/.claude. Claude Code supports relocating it via the CLAUDE_CONFIG_DIR env var (mine is ~/.claude-work), and transcripts then live under $CLAUDE_CONFIG_DIR/projects/<project>/<session>.jsonl. The plugin hardcodes the default root:
plugins/codex/scripts/lib/claude-session-transfer.mjs:8
const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), ".claude", "projects");
Two separate failures follow.
1. Plugin-side path validation. resolveClaudeSessionPath() rejects any transcript outside ~/.claude/projects. Worse, the realpathSync of the source and of CLAUDE_PROJECTS_DIR share one try/catch, so when the default root doesn't exist the user gets a wrong diagnosis pointing at a file that does exist:
Claude session file not found: ~/.claude-work/projects/-Users-...-my-repo/<uuid>.jsonl
2. Codex-side detection. Even with the path accepted, codex-cli 0.153.0 only detects Claude transcripts under ~/.claude/projects, so importedThreadIdForSource() finds no ledger record and the import fails with:
Codex reported that the Claude import completed, but did not record an imported thread. Check the Codex app-server logs for the underlying import error.
strings on the codex binary shows no CLAUDE_CONFIG_DIR occurrence at all (external-agent-migration/src/source_cla.rs), so the Rust side appears to assume the default root too. That part may need a fix in openai/codex rather than here, but the plugin can work around it.
Steps to Reproduce
- Run Claude Code with
CLAUDE_CONFIG_DIR=~/.claude-work (any non-default value; the default ~/.claude may also exist and be non-empty).
- In a session, run
/codex:transfer.
- Failure 1 (path validation) fires. Patch
CLAUDE_PROJECTS_DIR to the real root and rerun — failure 2 (ledger) fires.
- Copy the same transcript to
~/.claude/projects/<same-project-dir>/ and pass it via --source: the transfer succeeds.
Expected behavior
/codex:transfer works when the transcript lives under $CLAUDE_CONFIG_DIR/projects/, and the error message for a genuinely missing file is distinguishable from a root-not-allowed one.
Actual behavior
Transfer is impossible without hand-copying the transcript into ~/.claude/projects/.
Suggested fix
In claude-session-transfer.mjs, resolve the accepted roots from CLAUDE_CONFIG_DIR (falling back to ~/.claude) instead of a single constant, and split the two realpathSync calls so a missing default root can't be reported as a missing source file.
Until the Codex CLI understands the relocated root, the plugin can stage a copy of the transcript into ~/.claude/projects/<project-dir>/ and hand Codex that path. Locally patching those two things makes /codex:transfer succeed:
Transferred the Claude session into a Codex thread with visible turn history.
Codex session ID: 01a0691d-...
Happy to open a PR for the plugin-side change if that shape looks right.
Notes
Not a duplicate of the existing "did not record an imported thread" reports — those are Windows path/hash issues with the transcript already under ~/.claude/projects. Here the root itself is different, and the same failure reproduces on macOS.
Environment
- OS: macOS 15 (Darwin 24.6.0), Apple Silicon
- Node.js: v24
- Shell: zsh
- Plugin version: 1.0.6 (
plugins/cache/openai-codex/codex/1.0.6)
- codex-cli: 0.153.0 (Homebrew)
CLAUDE_CONFIG_DIR=~/.claude-work
Description
/codex:transfercannot transfer a session when Claude Code's config dir is not~/.claude. Claude Code supports relocating it via theCLAUDE_CONFIG_DIRenv var (mine is~/.claude-work), and transcripts then live under$CLAUDE_CONFIG_DIR/projects/<project>/<session>.jsonl. The plugin hardcodes the default root:plugins/codex/scripts/lib/claude-session-transfer.mjs:8Two separate failures follow.
1. Plugin-side path validation.
resolveClaudeSessionPath()rejects any transcript outside~/.claude/projects. Worse, therealpathSyncof the source and ofCLAUDE_PROJECTS_DIRshare one try/catch, so when the default root doesn't exist the user gets a wrong diagnosis pointing at a file that does exist:2. Codex-side detection. Even with the path accepted,
codex-cli 0.153.0only detects Claude transcripts under~/.claude/projects, soimportedThreadIdForSource()finds no ledger record and the import fails with:stringson the codex binary shows noCLAUDE_CONFIG_DIRoccurrence at all (external-agent-migration/src/source_cla.rs), so the Rust side appears to assume the default root too. That part may need a fix inopenai/codexrather than here, but the plugin can work around it.Steps to Reproduce
CLAUDE_CONFIG_DIR=~/.claude-work(any non-default value; the default~/.claudemay also exist and be non-empty)./codex:transfer.CLAUDE_PROJECTS_DIRto the real root and rerun — failure 2 (ledger) fires.~/.claude/projects/<same-project-dir>/and pass it via--source: the transfer succeeds.Expected behavior
/codex:transferworks when the transcript lives under$CLAUDE_CONFIG_DIR/projects/, and the error message for a genuinely missing file is distinguishable from a root-not-allowed one.Actual behavior
Transfer is impossible without hand-copying the transcript into
~/.claude/projects/.Suggested fix
In
claude-session-transfer.mjs, resolve the accepted roots fromCLAUDE_CONFIG_DIR(falling back to~/.claude) instead of a single constant, and split the tworealpathSynccalls so a missing default root can't be reported as a missing source file.Until the Codex CLI understands the relocated root, the plugin can stage a copy of the transcript into
~/.claude/projects/<project-dir>/and hand Codex that path. Locally patching those two things makes/codex:transfersucceed:Happy to open a PR for the plugin-side change if that shape looks right.
Notes
Not a duplicate of the existing "did not record an imported thread" reports — those are Windows path/hash issues with the transcript already under
~/.claude/projects. Here the root itself is different, and the same failure reproduces on macOS.Environment
plugins/cache/openai-codex/codex/1.0.6)CLAUDE_CONFIG_DIR=~/.claude-work