Claude Code skill import rewrites `.claude/` paths to `.Codex/`, producing directories that don't exist

Open 💬 4 comments Opened Aug 22, 2026 by chowdhuryaj

Summary

The external-agent import that copies Claude Code skills into ~/.agents/skills/
substitutes the token claudeCodex 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

  1. Have a Claude Code skill at ~/.claude/skills/example/SKILL.md whose body

references a real path, e.g.:

``
Session transcripts live in ~/.claude/projects/<slug>/
See also .claude/commands/example.md
``

  1. Let the Codex desktop onboarding import run (`[desktop]

external-agent-import-sync-enabled = true`).

  1. 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:

  1. Wrong case. Codex is 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.

  1. 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.mdAGENTS.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.

View original on GitHub ↗

4 Comments

chowdhuryaj · 5 days ago

Follow-up: the same "rewrite assumes the target exists" flaw applies to the
CLAUDE.mdAGENTS.md substitution, which I'd called correct in the original
report. 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 has AGENTS.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.md in the repo root — it is
authoritative", 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 to
AGENTS.md if <dir>/AGENTS.md exists; otherwise leave the original. That is a
cheap stat per candidate and it degrades safely — worst case you keep a name
that is merely off-brand, instead of emitting one that is wrong.

The same conditional would fix the path case in the original report: .claude/x
should only become .codex/x if ~/.codex/x exists.

matebenyovszky · 5 days ago

Hit this on mixed Claude Code + Codex machines too. Blind claudeCodex inside a skill body is the wrong layer: ~/.claude/projects/ is a real directory, and CLAUDE.mdAGENTS.md is 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 --apply

That writes .codex/skills and .codex/config.toml without substituting claude inside SKILL.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.

charle-z · 5 days ago

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-v4
Final head: cc3b3697867dbd3854c84c90982bca05f836afe6
Exact upstream base: c9b19deb09c1841ce7acc33ddb96276030936a29
Branch: 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.rs
  • codex-rs/external-agent-migration/src/rewrite_tests.rs

The 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, and C:\\Users\\...\\.claude\\plans, while standalone Claude Code prose still becomes Codex.

Exact-current-main focused validation is fully green:

  • exact source-shape guard: passed (head cc3b369..., 1 ahead / 0 behind / exactly 2 files)
  • just test -p codex-external-agent-migration: passed
  • just fix -p codex-external-agent-migration: passed
  • repository Rust formatting check: passed
  • final clean-tree (git diff --exit-code) check: passed

Validation run: https://github.com/charle-z/codex/actions/runs/32652611137

The validation base made the unrelated blocking-ci and v8-canary workflows 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.md token may refer to a real file but no AGENTS.md exists. RewriteProfile::rewrite() receives content only and is shared across skills/subagents/hooks, so there is no trustworthy filesystem base for a stat() 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.

charle-z · 1 day ago

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, and claude://session?id=claude.

Updated branch: charle-z:fix/40147-preserve-external-agent-references-v5
Head: 46d20eff12cfc8611194e47fa19a8e696192a976
Upstream base: 10d5a603aecbd73a38f3a6576cce69a78f8d6f1d

v5 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; standalone CLAUDE.md -> AGENTS.md behavior is intentionally unchanged.

Validation: Rust 1.95.0, cargo-nextest 0.9.143; focal URL regression 1/1; codex-external-agent-migration 175/175; Clippy with -D warnings; just fix; just fmt; git diff --check. Diff remains limited to rewrite.rs and rewrite_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.