Claude ↔ Codex Skill Porter
[Created by Opus: 3bad6cf7-0552-419a-92b7-5bc7cbeac4c8]
Generated: 2026-01-05 Claude Code: v2.0.76 (~/swe/claude-code-2.0.76/cli.js) Codex CLI: v0.77.0 (~/swe/codex.0.77.0/codex-rs/target/release/codex)
TL;DR for Agents
Can I just cp -r?
Yes, if the skill follows the skillname/SKILL.md directory structure.
# User-level
cp -r ~/.claude/skills/my-skill ~/.codex/skills/
cp -r ~/.codex/skills/my-skill ~/.claude/skills/
# Project-level
cp -r .claude/skills/my-skill .codex/skills/
cp -r .codex/skills/my-skill .claude/skills/
Gotchas Checklist (MUST verify before copying)
| Check |
Behavior |
Fix |
| Skill is a symlink |
Codex: Silently ignored |
Use real copy, not symlink |
Directory starts with . |
Codex: Silently ignored |
Remove leading dot from dirname |
name field > 64 chars |
Codex: Rejected with error |
Truncate name |
description > 1024 chars |
Codex: Rejected with error |
Truncate description |
Loose .md file (no wrapper dir) |
Codex: Not discovered |
Wrap in skillname/SKILL.md structure |
File not named SKILL.md exactly |
Both: Not discovered |
Rename to SKILL.md (case-sensitive) |
| Claude home dir varies |
Claude: Wrong path = not found |
Check $CLAUDECONFIGDIR; default is ~/.claude |
Quick Validation Command
# Check name/description lengths before copying to Codex
awk '/^---$/,/^---$/' ~/.claude/skills/my-skill/SKILL.md | \
grep -E '^(name|description):' | \
while read line; do
field=$(echo "$line" | cut -d: -f1)
value=$(echo "$line" | cut -d: -f2-)
len=${#value}
if [[ "$field" == "name" && $len -gt 64 ]]; then
echo "WARNING: name is $len chars (max 64)"
elif [[ "$field" == "description" && $len -gt 1024 ]]; then
echo "WARNING: description is $len chars (max 1024)"
fi
done
Skill Paths by Level
Claude Code
| Level |
Path |
Notes |
| User |
~/.claude/skills/ |
Default Claude Code config |
| Project |
.claude/skills/ |
Repo root or nested |
| Plugin |
~/.claude/plugins/marketplaces/.../plugins/{name}/skills/ |
Via plugin system |
Codex CLI
| Level |
Path |
Scope Enum |
Priority |
| Repo |
$REPO_ROOT/.codex/skills/ |
SkillScope::Repo |
1 (highest) |
| User |
~/.codex/skills/ |
SkillScope::User |
2 |
| System |
~/.codex/skills/.system/ |
SkillScope::System |
3 |
| Admin |
/etc/codex/skills/ (Unix only) |
SkillScope::Admin |
4 (lowest) |
Deduplication: By skill name. First match wins (repo > user > system > admin).
Format Comparison
Both use identical core format:
---
name: my-skill
description: When to use this skill...
---
# Markdown body here
Field Differences
| Field |
Claude |
Codex |
name |
Required, no length limit |
Required, max 64 chars |
description |
Required, no length limit |
Required, max 1024 chars |
version |
Optional, ignored by Codex |
Ignored |
license |
Optional, ignored by Codex |
Ignored |
metadata.short-description |
Ignored |
Optional, max 1024 chars |
Conversion Rules
Claude → Codex:
- Ensure
name ≤ 64 characters
- Ensure
description ≤ 1024 characters
- Remove
version/license (optional, Codex ignores them anyway)
- Optionally add
metadata.short-description
Codex → Claude:
- Remove
metadata.short-description (optional, Claude ignores it)
- Optionally add
version, license
Gotcha Details
1. Symlinks Are Silently Ignored (Codex only)
Source code evidence:
- File:
codex-rs/core/src/skills/loader.rs
- Search:
filetype.issymlink()
- Lines 207-209:
if file_type.is_symlink() {
continue;
}
Symptom: Skill doesn't appear in Codex skill list, no error message.
Fix: Use cp -r instead of ln -s.
2. Hidden Directories Skipped (Codex only)
Source code evidence:
- File:
codex-rs/core/src/skills/loader.rs
- Search:
filename.startswith('.')
- Lines 199-201:
if file_name.starts_with('.') {
continue;
}
Symptom: Skill in .my-skill/SKILL.md not discovered.
Fix: Rename directory to remove leading dot.
Exception: .system/ is explicitly handled for system skills.
3. Name Length Limit (Codex only)
Source code evidence:
- File:
codex-rs/core/src/skills/loader.rs
- Search:
MAXNAMELEN
- Line 37:
const MAXNAMELEN: usize = 64;
- Line 252:
validatefield(&name, MAXNAME_LEN, "name")?;
Symptom: Error in skill loading: invalid name: exceeds maximum length of 64 characters
Fix: Truncate name to 64 characters.
4. Description Length Limit (Codex only)
Source code evidence:
- File:
codex-rs/core/src/skills/loader.rs
- Search:
MAXDESCRIPTIONLEN
- Line 38:
const MAXDESCRIPTIONLEN: usize = 1024;
- Line 253:
validatefield(&description, MAXDESCRIPTION_LEN, "description")?;
Symptom: Error: invalid description: exceeds maximum length of 1024 characters
Fix: Truncate or summarize description.
5. Whitespace Normalization (Codex only)
Source code evidence:
- File:
codex-rs/core/src/skills/loader.rs
- Search:
sanitizesingleline
- Lines 273-275:
fn sanitize_single_line(raw: &str) -> String {
raw.split_whitespace().collect::<Vec<_>>().join(" ")
}
Effect: Newlines and extra spaces in name/description collapsed to single spaces.
Impact: Usually harmless, but be aware multiline YAML descriptions become single-line.
6. Loose .md Files (Claude allows, Codex doesn't)
Claude: Allows ~/.claude/skills/my-notes.md (file directly in skills dir)
Codex: Requires ~/.codex/skills/my-skill/SKILL.md (directory wrapper)
Source code evidence:
- File:
codex-rs/core/src/skills/loader.rs
- Search:
SKILLS_FILENAME
- Line 33:
const SKILLS_FILENAME: &str = "SKILL.md";
- Line 216:
if filetype.isfile() && filename == SKILLSFILENAME {
Fix: Wrap loose file in directory:
mkdir ~/.codex/skills/my-notes
mv my-notes.md ~/.codex/skills/my-notes/SKILL.md
7. Case-Sensitive Filename (Both)
Codex requires: SKILL.md exactly (uppercase)
Fix: Rename skill.md → SKILL.md
8. Claude Home Directory (Claude only)
Default: Claude Code's home directory is ~/.claude/
How to detect: Check the CLAUDECONFIGDIR environment variable:
echo $CLAUDE_CONFIG_DIR
# If empty → ~/.claude/
What agents must do:
- Before copying skills, determine the active Claude home:
``bash CLAUDEHOME="${CLAUDECONFIG_DIR:-$HOME/.claude}" ``
- Use the correct path for skill operations:
``bash cp -r ~/.codex/skills/my-skill "$CLAUDE_HOME/skills/" ``
- When creating new skills for Claude, install to
~/.claude/skills/
FAQ: Top 5 Agent Questions
Q1: "Can I copy the entire skills directory at once?"
Yes, with caveats:
cp -r ~/.claude/skills/* ~/.codex/skills/
But run validation afterward—any skill with oversized name/description will fail silently in Codex.
Q2: "The skill copied but doesn't appear in Codex. Why?"
Check in order:
- Is it a symlink? → Use real copy
- Does directory start with
.? → Rename
- Is file named exactly
SKILL.md? → Rename
- Is skill inside a wrapper directory? → Create one
- Check
name/description lengths
Q3: "Do bundled resources (scripts/, references/, assets/) copy over?"
Yes. Both systems support the same directory structure. cp -r copies everything.
Q4: "What about plugin-based Claude skills?"
Plugin skills live in ~/.claude/plugins/marketplaces/.../plugins/{plugin}/skills/. These use a different discovery mechanism. To port:
- Copy skill directory to
~/.codex/skills/
- Verify SKILL.md frontmatter meets Codex requirements
Q5: "How do I verify a skill loaded correctly in Codex?"
# List all discovered skills (requires Codex running)
codex --list-skills
# Or check for errors during load
codex --verbose 2>&1 | grep -i skill
Conversion Script (Optional)
For bulk conversion with validation:
#!/bin/bash
# Usage: ./convert-skill.sh <source> <dest>
# Example: ./convert-skill.sh ~/.claude/skills/my-skill ~/.codex/skills/my-skill
src="$1"
dest="$2"
# Validate source
if [[ ! -f "$src/SKILL.md" ]]; then
echo "ERROR: $src/SKILL.md not found"
exit 1
fi
# Extract and validate fields
name=$(awk '/^---$/,/^---$/' "$src/SKILL.md" | grep '^name:' | cut -d: -f2- | xargs)
desc=$(awk '/^---$/,/^---$/' "$src/SKILL.md" | grep '^description:' | cut -d: -f2- | xargs)
if [[ ${#name} -gt 64 ]]; then
echo "WARNING: name is ${#name} chars (max 64 for Codex)"
fi
if [[ ${#desc} -gt 1024 ]]; then
echo "WARNING: description is ${#desc} chars (max 1024 for Codex)"
fi
# Copy
cp -r "$src" "$dest"
echo "Copied $src → $dest"
Source Code Reference
| Component |
File |
Key Functions/Constants |
| Codex loader |
codex-rs/core/src/skills/loader.rs |
loadskills, parseskillfile, discoverskillsunderroot |
| Codex constants |
codex-rs/core/src/skills/loader.rs:33-39 |
SKILLSFILENAME, MAXNAMELEN, MAXDESCRIPTION_LEN |
| Codex validation |
codex-rs/core/src/skills/loader.rs:277-292 |
validatefield, sanitizesingle_line |
| Codex scope enum |
codex-rs/protocol/src/protocol.rs:1717-1725 |
SkillScope::{User,Repo,System,Admin} |
| Claude loader |
cli.js (compiled) |
Search: skillsPath, d62(), m62() |