Skip to content

/codex:transfer is broken when CLAUDE_CONFIG_DIR is set — Claude transcript root hardcoded to ~/.claude/projects #721

Description

@MatthewMaker

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

  1. Run Claude Code with CLAUDE_CONFIG_DIR=~/.claude-work (any non-default value; the default ~/.claude may also exist and be non-empty).
  2. In a session, run /codex:transfer.
  3. Failure 1 (path validation) fires. Patch CLAUDE_PROJECTS_DIR to the real root and rerun — failure 2 (ledger) fires.
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions