基于 Anthropic Claude Code 官方文档、OpenAI Codex 公开资料,以及本机 ~/.claude/~/.codex/~/plugins/ 实际目录结构实测整理。重点解释两个平台对 Skill、Plugin、Hook 的存放、加载、缓存机制差异,以及”一仓两用”软链接方案的原理。

一、概念层:Skill、Plugin、Hook 分别是什么

Skill

Skill 是一个可复用的任务流程定义,核心文件是 SKILL.md

skill-name/
├── SKILL.md          ← 任务说明(frontmatter + 正文)
├── scripts/          ← 可执行脚本
├── references/       ← 参考文件
└── assets/           ← 资源文件

两个平台的 Skill 格式完全一致,SKILL.md 可以跨平台复用。
Skill 的核心设计是渐进式加载(两端都是这个模式):

会话启动 → 只加载 name + description(轻量,占上下文很少)
用户触发 → 加载 SKILL.md 正文
执行中    → 按需读取 references / scripts / assets

所以装几十个 skill 不会撑爆上下文——大部分时候只有一行 description 在上下文里。

Plugin

Plugin 是分发容器,把多个 Skill、Hook、MCP、Agent、Command 打包成一个可安装的单元。

my-plugin/
├── .claude-plugin/plugin.json   ← CC manifest
├── .codex-plugin/plugin.json    ← Codex manifest(可同时存在)
├── skills/
├── hooks/
├── commands/
├── agents/
├── .mcp.json
└── bin/

Plugin 的关键区别于 Skill:

维度Skill(散装)Plugin(打包)
本质单个 SKILL.md含 plugin.json 的目录包
调用方式/skill-name/plugin-name:skill-name
包含内容单个任务流程Skills + Hooks + MCP + Agents + Commands
版本号无(或 frontmatter 里自定义)semver(影响缓存路径)
分发方式手动复制到 ~/.claude/skills/通过 marketplace 安装
命名冲突按优先级覆盖命名空间隔离,不冲突

Hook

Hook 是生命周期命令,在特定事件发生时自动执行 shell 命令。
常见事件:

UserPromptSubmit   用户提交消息时
PreToolUse         工具调用前(可阻止执行)
PostToolUse        工具调用后
Stop               回合结束时
SessionStart       会话启动时

Hook 不是”给模型看的说明”,而是真的执行 shell command。所以它对路径、环境变量、执行权限非常敏感——这也是最容易踩坑的地方。

二、CC 的 Skill/Plugin 存放与加载机制

Skill 加载的四个来源

CC 从四个层级发现 Skill,优先级从高到低:

层级路径命名空间说明
Enterprise通过 managed settings 定义裸名企业管理,最高优先级
Personal~/.claude/skills/<name>/SKILL.md裸名 /name用户级,手动放置或工具安装
Project<project>/.claude/skills/<name>/SKILL.md裸名 /name项目级,跟随仓库
Plugin插件目录下的 skills/<name>/SKILL.mdplugin:name命名空间隔离

冲突规则

  • Enterprise > Personal > Project(同名时高层级覆盖低层级)
  • Plugin skills 因为有独立命名空间,不会和任何层级冲突
  • 例如 ~/.claude/skills/mgitiplugin 插件里的 skills/mgit 并存——前者叫 /mgit,后者叫 /iplugin:mgit

两阶段加载细节

Phase 1:会话启动 — 描述列表

  • 所有已发现 skill 的 name + description(来自 frontmatter)注入 system-reminder
  • 每个 skill 描述上限 1,536 字符
  • 总预算为模型上下文窗口的 1%
  • 超预算时,最少使用的 skill 的 description 被丢弃(name 始终保留)

Phase 2:触发后 — 全文加载

  • 用户输入 /skill-name 或模型自动匹配到某 skill 时,加载 SKILL.md 正文
  • 加载后留在上下文中直到会话结束
  • 上下文压缩时:每个 skill 保留前 5,000 tokens,最多 25,000 tokens 总预算,最近调用的优先

Plugin 的目录结构(本机实际)

~/.claude/plugins/
├── installed_plugins.json       ← 安装注册表
├── known_marketplaces.json      ← 市场来源注册
├── cache/                       ← 版本化缓存(仅 git-based 市场用)
│   └── claude-plugins-official/
│       ├── skill-creator/205b6e0b3036/     ← git SHA 做版本号
│       └── frontend-design/205b6e0b3036/
├── marketplaces/                ← 市场目录
│   ├── claude-plugins-official/ ← git clone 的官方市场
│   ├── iplugin/                 ← 本地目录市场
│   │   └── iplugin -> /Users/markz/code/tools/iplugin  ← ★ 软链接
│   └── RDxiaogang/
├── data/                        ← ${CLAUDE_PLUGIN_DATA},持久数据
└── local/                       ← 手动注册的本地插件

CC 的缓存机制

CC 的缓存(~/.claude/plugins/cache/只对 git-based 市场插件生效:

  • claude-plugins-official 从 GitHub 拉取,用 git commit SHA 做版本号
  • 旧版本标记 .orphaned_at,7 天后自动清理
  • .in_use/ 目录通过 PID 文件追踪活跃会话,防止清理正在使用的版本

关键点:本地目录市场不走 cache

  • iplugin 注册为本地目录市场,CC 直接通过 marketplace 目录下的软链接访问源码
  • 不存在版本化缓存副本
  • 源码改动即时生效(不需要重装/升级)

CC 的环境变量

变量含义
${CLAUDE_PLUGIN_ROOT}插件安装目录(可能是 cache 路径,也可能是 marketplace 路径)
${CLAUDE_PLUGIN_DATA}持久数据目录 ~/.claude/plugins/data/{id}/,跨版本存活

三、Codex 的 Skill/Plugin 存放与加载机制

Personal Marketplace 的发现机制

Codex 不会扫描整个 home 目录。它的发现流程是:

config.toml:
  [marketplaces.personal]
  source = "/Users/markz"

寻找 /Users/markz/.agents/plugins/marketplace.json

marketplace.json 显式列出每个插件及其相对路径:
  { "name": "iplugin", "source": { "path": "./plugins/iplugin" } }

解析 /Users/markz/plugins/iplugin(软链接 → 源码仓库)

所以 ~/plugins/ 不是 Codex 自动发现的约定目录,而是 marketplace.json 里配置的相对路径。
关键文件:/Users/markz/.agents/plugins/marketplace.json

版本化缓存(核心差异)

Codex 对所有已启用插件都会创建版本化缓存副本:

~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/

缓存行为:

  • 版本号来自 .codex-plugin/plugin.jsonversion 字段
  • 全量复制源码目录到缓存(包括 .git 目录)
  • 会话启动时检测:若源码 version 变化 → 创建新缓存 → 清理旧版本
  • Personal marketplace 的旧版本会被自动清理(只保留当前版本)
  • Bundled marketplace 可能保留多个版本

这就是和 CC 的核心区别:

  • CC 本地目录市场:直接读源码(通过软链接),不复制
  • Codex personal marketplace:强制复制到版本化缓存,运行时用缓存副本

Codex 的目录结构(本机实际)

~/.codex/
├── config.toml                  ← 主配置(插件启用、hook trust、MCP 等)
├── plugins/
│   └── cache/
│       ├── personal/
│       │   └── iplugin/
│       │       └── 0.13.6/      ← 当前版本的完整副本
│       ├── openai-bundled/      ← Codex App 内置插件
│       └── openai-curated/      ← 审核过的第三方插件
├── hooks/
│   └── iplugin-skill-telemetry.py  ← 稳定 wrapper
└── skill-usage.jsonl            ← telemetry 日志
 
~/plugins/
└── iplugin -> /Users/markz/code/tools/iplugin  ← 软链接
 
~/.agents/plugins/marketplace.json  ← personal marketplace 索引

Codex 的环境变量

变量含义
${PLUGIN_ROOT}插件缓存目录(如 ~/.codex/plugins/cache/personal/iplugin/0.13.6/
${PLUGIN_DATA}持久数据目录
${CODEX_HOME}Codex 配置根目录(~/.codex/

Codex 的 Hook 信任机制

Codex 独有的安全机制——每个 hook 都有 SHA-256 hash 验证:

[hooks.state."iplugin@personal:hooks/codex-hooks.json:post_tool_use:0:0"]
trusted_hash = "sha256:01acb9c..."
enabled = true

当 hook 命令内容变化时,hash 改变,需要重新 trust(通过 /hooks)。CC 没有这个机制。

四、两端对比总表

维度Claude Code (CC)Codex
配置格式JSON(settings.json + 多个注册文件)TOML(config.toml 统一管理)
Plugin manifest.claude-plugin/plugin.json.codex-plugin/plugin.json
市场发现known_marketplaces.json 登记市场来源config.toml + .agents/plugins/marketplace.json
本地插件加载方式软链接透传,直接读源码全量复制到版本化缓存目录
缓存路径~/.claude/plugins/cache/{market}/{plugin}/{sha}/~/.codex/plugins/cache/{market}/{plugin}/{version}/
版本号来源git commit SHA 或 semverplugin.json 的 version 字段
旧版本清理7 天 orphan 自动清理新版本到来时立即清理旧版本
Skill 命名空间plugin:skill(插件)/ 裸名(用户/项目)类似
Hook 路径变量${CLAUDE_PLUGIN_ROOT}${PLUGIN_ROOT}
Hook 路径稳定性稳定(本地市场直接指向软链接→源码)不稳定(指向版本化缓存,升级后可能失效)
Hook 信任机制无 hash 验证SHA-256 hash,变更后需重新 trust
持久数据${CLAUDE_PLUGIN_DATA} 跨版本存活${PLUGIN_DATA}
活跃会话追踪.in_use/ PID 文件

五、“一仓两用”软链接方案

方案结构

/Users/markz/code/tools/iplugin/         ← 唯一真实源码(git 仓库)
├── .claude-plugin/plugin.json           ← CC manifest
├── .codex-plugin/plugin.json            ← Codex manifest
├── skills/                              ← 共用(SKILL.md 格式通用)
├── hooks/
│   ├── hooks.json                       ← CC 专用 hook 配置
│   ├── codex-hooks.json                 ← Codex 专用 hook 配置
│   └── skill-telemetry.py              ← 共用脚本
└── ...
 
        ┌─── 软链接 ───┐
        v               v
CC 侧                   Codex 侧
~/.claude/plugins/      ~/plugins/
  marketplaces/iplugin/   iplugin -> 源码
    iplugin -> 源码

两端的实际加载路径

CC 侧(零成本,即时生效):

settings.json hook:
  "python3 ~/.claude/plugins/marketplaces/iplugin/iplugin/hooks/skill-telemetry.py"
      ↓ 解引用软链接
  /Users/markz/code/tools/iplugin/hooks/skill-telemetry.py

源码改动即时生效。不走缓存。永远不会因版本号变化而失效。
Codex 侧(有额外成本):

marketplace.json → ~/plugins/iplugin → 源码仓库
      ↓ Codex 读取 .codex-plugin/plugin.json 的 version
      ↓ 全量复制到缓存
~/.codex/plugins/cache/personal/iplugin/0.13.6/
      ↓ ${PLUGIN_ROOT} 指向缓存
hook 执行:缓存目录里的脚本

源码改动不会立即生效——需要升级 version → Codex 重建缓存 → 新会话生效。

优劣分析

维度CC 侧Codex 侧
源码改动生效即时需要升级 version + 新会话
路径稳定性稳定升级后旧会话可能报错
磁盘开销无额外开销每个版本全量复制一份
维护成本需要稳定 wrapper 或手动清旧缓存
git 操作只需操作源码仓库只需操作源码仓库(缓存自动跟随)

注意事项

  1. Codex version 字段是触发缓存刷新的关键——只改源码不改 version,Codex 不会重建缓存
  2. 长期运行的旧 Codex 会话可能持有旧版本的 ${PLUGIN_ROOT},即使新缓存已就位
  3. Hook 命令变更后 Codex 需要重新 trust(hash 变化),CC 无此问题
  4. CC 的 installed_plugins.json 可能记录旧 version——不影响运行,但 /plugin update 可能行为不符预期
  5. Skills 本身是完全通用的——SKILL.md 格式两端一致,无需任何适配

六、版本缓存路径失效问题(iPlugin PostToolUse 报错案例)

现象

PostToolUse hook (completed)
feedback: python3: can't open file
  '~/.codex/plugins/cache/personal/iplugin/0.13.0/hooks/skill-telemetry.py':
  [Errno 2] No such file or directory

根因

旧会话的 ${PLUGIN_ROOT} 仍指向 0.13.0,但该版本缓存已被清理(当前只有 0.13.6)。

修复:稳定 Wrapper

把 Codex hook 入口从版本缓存里拿出来:

旧方案:${PLUGIN_ROOT}/hooks/skill-telemetry.py
  ↓ 依赖版本缓存路径,升级后可能失效
 
新方案:$HOME/.codex/hooks/iplugin-skill-telemetry.py
  ↓ 固定位置的 wrapper,再动态定位真实脚本

Wrapper 查找优先级:

1. 环境变量 IPPLUGIN_TELEMETRY_SCRIPT(手动覆盖)
2. /Users/markz/code/tools/iplugin/hooks/skill-telemetry.py(源码仓库)
3. ~/.codex/plugins/cache/personal/iplugin/*/hooks/skill-telemetry.py(最新缓存)

任何路径都找不到时静默退出,不再污染工具输出。

为什么 CC 不需要这个修复

因为 CC 的 hook 路径指向 marketplace 软链接 → 源码,不经过版本化缓存。版本号怎么变都不影响路径有效性。

资料来源


相关笔记