# Claude Codex Skill Porter

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

- Skill: `tankygranny05/claude-codex-skill-porter` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tankygranny05/claude-codex-skill-porter`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tankygranny05/claude-codex-skill-porter/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tankygranny05 (https://skillmd.com/u/tankygranny05)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/tankygranny05/claude-codex-skill-porter

---


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

| 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 `$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

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

```yaml
---
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:**
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.md` → `SKILL.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

| Component | File | Key Functions/Constants |
|-----------|------|------------------------|
| Codex loader | `codex-rs/core/src/skills/loader.rs` | `load_skills`, `parse_skill_file`, `discover_skills_under_root` |
| Codex constants | `codex-rs/core/src/skills/loader.rs:33-39` | `SKILLS_FILENAME`, `MAX_NAME_LEN`, `MAX_DESCRIPTION_LEN` |
| Codex validation | `codex-rs/core/src/skills/loader.rs:277-292` | `validate_field`, `sanitize_single_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()` |

