AgentSkillsCN

claude-codex-skill-porter

在 Claude Code 与 Codex CLI 格式之间转换技能。适用于将技能从 ~/.claude/skills 迁移到 ~/.codex/skills(或反之亦然),在 .claude/skills 与 .codex/skills 目录之间转换项目级技能,或排查已复制技能为何无法加载的问题。涵盖所有技能层级(用户、项目/仓库、系统、管理员),并解决符号链接、隐藏目录以及字段长度限制等常见陷阱。

SKILL.md
--- frontmatter
name: claude-codex-skill-porter
description: Convert skills between Claude Code and Codex CLI formats. Use when porting skills from ~/.claude/skills to ~/.codex/skills (or vice versa), converting project-level skills between .claude/skills and .codex/skills directories, or troubleshooting why a copied skill isn't loading. Covers all skill levels (user, project/repo, system, admin) and gotchas like symlinks, hidden dirs, and field length limits.

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.

bash
# 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)

CheckBehaviorFix
Skill is a symlinkCodex: Silently ignoredUse real copy, not symlink
Directory starts with .Codex: Silently ignoredRemove leading dot from dirname
name field > 64 charsCodex: Rejected with errorTruncate name
description > 1024 charsCodex: Rejected with errorTruncate description
Loose .md file (no wrapper dir)Codex: Not discoveredWrap in skillname/SKILL.md structure
File not named SKILL.md exactlyBoth: Not discoveredRename to SKILL.md (case-sensitive)
Claude home dir variesClaude: Wrong path = not foundCheck $CLAUDE_CONFIG_DIR; default is ~/.claude

Quick Validation Command

bash
# 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

LevelPathNotes
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

LevelPathScope EnumPriority
Repo$REPO_ROOT/.codex/skills/SkillScope::Repo1 (highest)
User~/.codex/skills/SkillScope::User2
System~/.codex/skills/.system/SkillScope::System3
Admin/etc/codex/skills/ (Unix only)SkillScope::Admin4 (lowest)

Deduplication: By skill name. First match wins (repo > user > system > admin).


Format Comparison

Both use identical core format:

yaml
---
name: my-skill
description: When to use this skill...
---

# Markdown body here

Field Differences

FieldClaudeCodex
nameRequired, no length limitRequired, max 64 chars
descriptionRequired, no length limitRequired, max 1024 chars
versionOptional, ignored by CodexIgnored
licenseOptional, ignored by CodexIgnored
metadata.short-descriptionIgnoredOptional, max 1024 chars

Conversion Rules

Claude → Codex:

  1. Ensure name ≤ 64 characters
  2. Ensure description ≤ 1024 characters
  3. Remove version/license (optional, Codex ignores them anyway)
  4. Optionally add metadata.short-description

Codex → Claude:

  1. Remove metadata.short-description (optional, Claude ignores it)
  2. 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: file_type.is_symlink()
  • Lines 207-209:
rust
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: file_name.starts_with('.')
  • Lines 199-201:
rust
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: MAX_NAME_LEN
  • Line 37: const MAX_NAME_LEN: usize = 64;
  • Line 252: validate_field(&name, MAX_NAME_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: MAX_DESCRIPTION_LEN
  • Line 38: const MAX_DESCRIPTION_LEN: usize = 1024;
  • Line 253: validate_field(&description, MAX_DESCRIPTION_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: sanitize_single_line
  • Lines 273-275:
rust
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 file_type.is_file() && file_name == SKILLS_FILENAME {

Fix: Wrap loose file in directory:

bash
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.mdSKILL.md

8. Claude Home Directory (Claude only)

Default: Claude Code's home directory is ~/.claude/

How to detect: Check the CLAUDE_CONFIG_DIR environment variable:

bash
echo $CLAUDE_CONFIG_DIR
# If empty → ~/.claude/

What agents must do:

  1. Before copying skills, determine the active Claude home:
    bash
    CLAUDE_HOME="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
    
  2. Use the correct path for skill operations:
    bash
    cp -r ~/.codex/skills/my-skill "$CLAUDE_HOME/skills/"
    
  3. 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:

bash
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:

  1. Is it a symlink? → Use real copy
  2. Does directory start with .? → Rename
  3. Is file named exactly SKILL.md? → Rename
  4. Is skill inside a wrapper directory? → Create one
  5. 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:

  1. Copy skill directory to ~/.codex/skills/
  2. Verify SKILL.md frontmatter meets Codex requirements

Q5: "How do I verify a skill loaded correctly in Codex?"

bash
# 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:

bash
#!/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

ComponentFileKey Functions/Constants
Codex loadercodex-rs/core/src/skills/loader.rsload_skills, parse_skill_file, discover_skills_under_root
Codex constantscodex-rs/core/src/skills/loader.rs:33-39SKILLS_FILENAME, MAX_NAME_LEN, MAX_DESCRIPTION_LEN
Codex validationcodex-rs/core/src/skills/loader.rs:277-292validate_field, sanitize_single_line
Codex scope enumcodex-rs/protocol/src/protocol.rs:1717-1725SkillScope::{User,Repo,System,Admin}
Claude loadercli.js (compiled)Search: skillsPath, d62(), m62()