Claude Code skill import rewrites `.claude/` paths to `.Codex/`, producing directories that don't exist
Summary
The external-agent import that copies Claude Code skills into ~/.agents/skills/
substitutes the token claude → Codex across the entire file body, including
inside literal filesystem paths. Prose retargeting is correct and useful; path
rewriting is not, because it produces paths that do not exist.
Repro
- Have a Claude Code skill at
~/.claude/skills/example/SKILL.mdwhose body
references a real path, e.g.:
````
Session transcripts live in ~/.claude/projects/<slug>/
See also .claude/commands/example.md
- Let the Codex desktop onboarding import run (`[desktop]
external-agent-import-sync-enabled = true`).
- Read the imported copy at
~/.agents/skills/example/SKILL.md.
Actual
Session transcripts live in ~/.Codex/projects/<slug>/
See also .Codex/commands/example.md
Expected
The path should be left as ~/.claude/projects/<slug>/. It is an absolute
reference to a real directory owned by another tool, not an agent-relative path
that has a Codex equivalent.
Why this breaks
Two separate problems:
- Wrong case.
Codexis the display name; the directory is.codex. On
case-insensitive APFS this happens to resolve, so it is silent on macOS and
would break on a case-sensitive volume or on Linux.
- No such directory — the real problem. Codex has no analogue for most of
these. On my machine, after import:
| rewritten to | exists? |
|---|---|
| ~/.codex/commands/ | no |
| ~/.codex/projects/ | no |
| ~/.codex/handoffs/ | no |
| ~/.codex/plans/ | no |
| ~/.codex/skills/ | yes (coincidence — same directory name) |
So every imported skill that cites a ~/.claude/<subdir>/ path now sends the
agent to a directory that does not exist. In my case that was 10 references
across 5 imported skills.
The prose substitutions in the same pass are correct and worth keeping —CLAUDE.md → AGENTS.md, "Claude Code UI" → "Codex UI". It is specifically the
path-shaped occurrences that should be exempt.
Suggested fix
Skip substitution for occurrences that look like filesystem paths — a match
preceded by /, ~/, or $HOME/, or immediately followed by /. A skill that
names another tool's data directory means that directory literally.
Failing that, at minimum substitute .claude → .codex rather than .Codex,
so the result is not also case-wrong.
Environment
- Codex CLI v0.149.0-alpha.4.1 (ChatGPT desktop, macOS)
- macOS 15 / Darwin 25.5.0, APFS (case-insensitive)
- Relevant module per binary strings:
codex_tui::external_agent_config_migration
Workaround
Set external-agent-import-sync-enabled = false under [desktop] in~/.codex/config.toml after the initial import, and repair the imported paths by
hand. The import is one-shot, so repairs survive normal codex exec use.
4 Comments
Follow-up: the same "rewrite assumes the target exists" flaw applies to the
CLAUDE.md→AGENTS.mdsubstitution, which I'd called correct in the originalreport. It is correct only when the referenced repo actually has an
AGENTS.md.Concrete case from the same import. Two skills each pointed at a project's root
instruction file:
| skill references | repo has
CLAUDE.md| repo hasAGENTS.md| rewrite result ||---|---|---|---|
| project A's root instruction file | yes | yes | fine |
| project B's root instruction file | yes | no | now points at a missing file |
For project B the imported skill says "read
AGENTS.mdin the repo root — it isauthoritative", and no such file exists. The authoritative file is still
CLAUDE.md, sitting right there unread.This matters more than the path case, because these rewrites land on content
instructions rather than incidental references. A skill whose first step is "read
the authoritative file" silently becomes a skill whose first step fails.
Suggested refinement to the fix: make the substitution conditional on the target
resolving. At import time, for a reference to
<dir>/CLAUDE.md, only rewrite toAGENTS.mdif<dir>/AGENTS.mdexists; otherwise leave the original. That is acheap
statper candidate and it degrades safely — worst case you keep a namethat is merely off-brand, instead of emitting one that is wrong.
The same conditional would fix the path case in the original report:
.claude/xshould only become
.codex/xif~/.codex/xexists.Hit this on mixed Claude Code + Codex machines too. Blind
claude→Codexinside a skill body is the wrong layer:~/.claude/projects/is a real directory, andCLAUDE.md→AGENTS.mdis only safe when that file exists.Until the importer skips path-shaped matches (and stats the rewrite target), I keep one playbook and write each tool’s native layout, and leave the skill body alone:
npx @agentplaybooks/cli@beta doctor .npx @agentplaybooks/cli@beta sync . --target=codex --applyThat writes
.codex/skillsand.codex/config.tomlwithout substitutingclaudeinsideSKILL.md. Same-named files with different content get reported and skipped.Still want the importer to refuse invented
.Codex/paths — a writer is a stopgap, not a fix.Final exact-head implementation/validation update
Root cause remains in the shared external-agent rewriter: replacements are selected with only ASCII word boundaries, so
/,\\, and.let source-tool names inside filesystem paths, URLs, and dotted identifiers be rewritten as though they were prose.Final source branch:
charle-z:fix/40147-preserve-external-agent-references-v4Final head:
cc3b3697867dbd3854c84c90982bca05f836afe6Exact upstream base:
c9b19deb09c1841ce7acc33ddb96276030936a29Branch: https://github.com/charle-z/codex/tree/fix/40147-preserve-external-agent-references-v4
The final branch is exactly one commit ahead / zero behind that upstream base and modifies only:
codex-rs/external-agent-migration/src/rewrite.rscodex-rs/external-agent-migration/src/rewrite_tests.rsThe patch keeps the existing prose rewrite behavior but skips matches that are syntactically part of literal source references: immediately adjacent to
/or\\, preceded by., or followed by a dotted identifier/hostname suffix. Regression coverage includes POSIX/Windows paths, dot-directories, URLs/dotted identifiers, path-qualified documentation names, and the concrete Claude paths~/.claude/projects,.claude/commands, andC:\\Users\\...\\.claude\\plans, while standaloneClaude Codeprose still becomesCodex.Exact-current-main focused validation is fully green:
cc3b369..., 1 ahead / 0 behind / exactly 2 files)just test -p codex-external-agent-migration: passedjust fix -p codex-external-agent-migration: passedgit diff --exit-code) check: passedValidation run: https://github.com/charle-z/codex/actions/runs/32652611137
The validation base made the unrelated
blocking-ciandv8-canaryworkflows manual-only, so this run was isolated to the issue-scoped job. The internal validation PR was closed without merge after the run.Scope note: this intentionally does not claim to solve the separate follow-up where a bare, unqualified
CLAUDE.mdtoken may refer to a real file but noAGENTS.mdexists.RewriteProfile::rewrite()receives content only and is shared across skills/subagents/hooks, so there is no trustworthy filesystem base for astat()decision. Guessing one would create another incorrect-rewrite class. The branch fixes the deterministic literal path/URL/dotted-reference corruption reported here.I attempted again to open an upstream draft PR from this exact-current-main branch, but GitHub returned
403 Resource not accessible by integration, consistent with this repository's collaborator-only cross-fork PR restriction. The branch/SHA/run above are the submit-ready artifact and evidence for maintainer comparison or cherry-pick.Correction to my previous v4 validation: adversarial URL cases found one remaining rewrite gap. v4 preserved path/host/dotted forms, but could still retarget source-tool names in URL query/fragment positions and URI schemes, e.g.
...?agent=claude#claude,...?q=Claude, andclaude://session?id=claude.Updated branch:
charle-z:fix/40147-preserve-external-agent-references-v5Head:
46d20eff12cfc8611194e47fa19a8e696192a976Upstream base:
10d5a603aecbd73a38f3a6576cce69a78f8d6f1dv5 keeps the fix syntactic: it recognizes source names inside
://URI tokens in addition to the existing path/dotted-reference checks. It does not stat the filesystem or add target-existence heuristics; standaloneCLAUDE.md->AGENTS.mdbehavior is intentionally unchanged.Validation: Rust 1.95.0, cargo-nextest 0.9.143; focal URL regression 1/1;
codex-external-agent-migration175/175; Clippy with-D warnings;just fix;just fmt;git diff --check. Diff remains limited torewrite.rsandrewrite_tests.rs. Upstream advanced during validation but did not touch either file; final rebase was clean.I did not retry the previously blocked cross-fork PR.