改一条 Skill 规则要改四处?一条 ln -s 让多个 AI 编码工具共享同一份配置
📝 摘要:Claude Code、Codex、QoderCN 国际版和国内版,Skill 格式一样但目录各不同,改一条规则要改四处。本文用软链做到「一份维护、处处生效」:真相源只存一份 SKILL.md,各工具 skills 目录
ln -s指过去。重点是三个反直觉的坑:Codex 项目级读的是中立目录.agents/skills而非.codex/skills;QoderCN 项目级只认项目根skills/;~/.agents/skills项目层一条顶一片、全局层只覆盖一小组工具。
写在前面
如果你跟我一样,同时跑着四个 AI 编码工具——Claude Code、Codex、QoderCN 国际版、QoderCN 国内版——那你大概率遇到过这个场景:
你写了一条新的 Skill 规则:“Java 内部方法调用必须加 this. 前缀”。
然后你发现:
- Claude Code 读
~/.claude/skills/ - Codex 读
~/.codex/skills/ - QoderCN 国际版读
~/.qoder/skills/ - QoderCN 国内版读
~/.qoder-cn/skills/
四个目录,四份文件。你建了四份。
过了两周,你想改一下这条规则的触发描述。你得打开四个文件,改四遍。
又过了一周,你同事问你这条规则到底在哪定义的,你指了指 .claude/skills/。同事说:"但 Codex 好像不认这个路径啊?"你沉默了三秒,默默又建了一条软链。
这就是"配置漂移"——同一个意图散落在多个物理位置,改一处漏两处,时间一长谁也不知道哪份是最新的。
折腾一圈之后,我想通了一件事:这四个工具的 Skill 格式完全相同(都是 SKILL.md + frontmatter),只是配置目录不一样。既然内容一样,为什么不只维护一份,然后用软链把四个目录串起来?
💡 如果你对 Skill 的概念还不太熟悉,建议先看一下 《CLAUDE.md 写到 500 行还管不住 AI?Skills 分层食用指南》,这篇把 Skill 和 CLAUDE.md 的区别讲得很清楚。本文聚焦在"多工具共享"这个环节。
一、问题本质:格式一样,目录不同
先把四个工具的 Skill 目录摆出来(注意是 Skill 目录,不是配置目录,下面会说这个区别有多坑):
| 工具 | 全局 Skill 目录 | 项目级 Skill 目录 |
|---|---|---|
| Claude Code | ~/.claude/skills/ |
.claude/skills/ |
| Codex | ~/.codex/skills/ |
.agents/skills/ |
| QoderCN 国际版 | ~/.qoder/skills/ |
项目根 skills/(⚠️ 见下) |
| QoderCN 国内版 | ~/.qoder-cn/skills/ |
项目根 skills/(⚠️ 见下) |
两个反直觉的点,都是踩过才知道的:
① 全局和项目不是「加个 ~ 」那么简单。 最典型的是 Codex:全局在 ~/.codex/skills/,项目级却不是 .codex/skills/,而是 .agents/skills/——一个跟工具名毫无关系的目录。你按「全局路径去掉 ~」的直觉去建项目级软链,建完不生效,还查不出为什么。
② 「配置目录」和「Skill 目录」是两回事,而且文档不一定对得上实现。 QoderCN 的 settings 之类确实读 .qoder/。公开的规范映射表里,它项目级 Skill 目录也写的是 .qoder/skills/——但我实测放那儿不生效,只有项目根的 skills/ 才被加载(详见 3.2)。
⚠️ 这条值得单独记一笔:这类映射表是社区维护的,未必跟得上每个工具的每个版本。真按表建完不生效,别怀疑人生,直接换个位置试——判据很简单:建完开一个新会话,问它「你现在加载了哪些 skill」,比读任何文档都快。
📌 顺带说,那个
.agents/不是 Codex 自创的——它是 Agent Skills 规范 定义的厂商中立目录,一批工具正在往上收敛。这件事对本文方案影响很大,单独放在第四节讲。
好在 Skill 文件的格式完全一样——一个 SKILL.md,frontmatter 里写 name 和 description,正文写规则内容。各工具加载的逻辑也一样:扫描自己那个 skills/ 目录,每个子目录就是一个 Skill。
唯一的区别就是目录名。
这就给软链创造了条件:只要把内容放在一个"物理目录"里,然后让四个工具的 skills/ 都指向它,问题就解决了。
二、核心思路:一份物理文件 + 多条软链
~/docs/team-docs/skills/ ← 真相源(单一事实源,唯一维护点)
├── common/ ← 全局通用那份
└── order-svc/ ← 项目专属那份
全局层软链(所有项目生效)
├ ~/.claude/skills/ ← Claude Code
├ ~/.codex/skills/ ← Codex
├ ~/.agents/skills/ ← 中立目录:Cline / Warp / Zed / Kimi Code CLI
├ ~/.qoder/skills/ ← QoderCN 国际版
└ ~/.qoder-cn/skills/ ← QoderCN 国内版
项目层软链(仅本仓库生效)
├ <project>/.claude/skills/ ← Claude Code
├ <project>/.agents/skills/ ← 中立目录:Codex / Cursor / Gemini CLI / Copilot / OpenCode…
└ <project>/skills/ ← QoderCN(项目根,两版本共用)
一句话总结:SKILL.md 共享一份,各工具的 README / settings 各自维护。
为什么不全量软链(把整个 .claude/ 链过去)?因为每个工具还有自己的配置文件——Claude Code 有 CLAUDE.md,QoderCN 有 settings.json,这些东西不该共享。只软链 skills/ 这一层,粒度刚好。
三、实操:两层软链,全局 + 项目
Skill 分两层——全局层(所有项目生效)和项目层(仅当前项目生效)。软链也要分两层建。
3.1 全局层
全局层放在用户主目录下,所有项目都会加载:
# 真相源只有一份,公共层放在:~/docs/team-docs/skills/common/
# Claude Code
mkdir -p ~/.claude && ln -s ~/docs/team-docs/skills/common ~/.claude/skills
# Codex
mkdir -p ~/.codex && ln -s ~/docs/team-docs/skills/common ~/.codex/skills
# QoderCN 国际版
mkdir -p ~/.qoder && ln -s ~/docs/team-docs/skills/common ~/.qoder/skills
# QoderCN 国内版
mkdir -p ~/.qoder-cn && ln -s ~/docs/team-docs/skills/common ~/.qoder-cn/skills
# 中立目录(Cline / Warp / Zed / Kimi Code CLI 这一组全局读它)
mkdir -p ~/.agents && ln -s ~/docs/team-docs/skills/common ~/.agents/skills
建完之后,这几个路径都指向同一个物理目录。你在里面新增一个 Skill,对应的工具同时生效。
⚠️ 全局层没有「一条通吃」的写法。
~/.agents/skills听着像万能钥匙,但它在全局层只覆盖 Cline、Warp、Zed、Kimi Code CLI 这一组;Codex 全局读的是~/.codex/skills/、Cursor 是~/.cursor/skills/、Gemini CLI 是~/.gemini/skills/——各建各的,一条都省不了。真正能「一条顶一片」的是项目层(见 3.2 和第四节)。
3.2 项目层
项目专属的 Skill 同样收在真相源里——~/docs/team-docs/skills/ 下的项目子目录(如 order-svc),各项目再软链接入。
这里有个和全局层不一样、且实测踩过的坑:项目级的 QoderCN 不认 .qoder/skills、.qoder-cn/skills 这种放在 dot 目录里的软链(全局层放 ~/.qoder/skills 是认的,项目层却不认)。项目级 QoderCN 只认项目根目录下的 skills/。所以项目层的 QoderCN 只需在项目根建一条 skills 软链,国际版、国内版就都生效了:
# 真相源:~/docs/team-docs/skills/order-svc(order-svc 项目专属那份)
# Claude Code:项目级读 <project>/.claude/skills,指过去
ln -s ~/docs/team-docs/skills/order-svc ~/repos/order-svc/.claude/skills
# Codex 及一大批工具:项目级读中立目录 <project>/.agents/skills(不是 .codex/skills!)
mkdir -p ~/repos/order-svc/.agents && \
ln -s ~/docs/team-docs/skills/order-svc ~/repos/order-svc/.agents/skills
# QoderCN(国际版 + 国内版共用这一条):软链建在项目根,名字就叫 skills
ln -s ~/docs/team-docs/skills/order-svc ~/repos/order-svc/skills
💡 上面第二条是本文更新过的写法。我最早按「全局
~/.codex/skills→ 项目级.codex/skills」的对称直觉建,建完 Codex 根本不加载。查了规范才知道,Codex 项目级读的是中立目录.agents/skills/——而且不只 Codex,Cursor、Gemini CLI、GitHub Copilot、OpenCode 等一批工具项目级全都读这个路径。这条软链的性价比因此高得离谱:一条顶一片。
⚠️ 别在项目里建
.qoder/skills/.qoder-cn/skills——实测不生效。项目级 QoderCN 只扫项目根的skills/;一条软链两个版本共用,比全局层还省一条。(全局层没有"项目根"这一说,仍按 3.1 给~/.qoder、~/.qoder-cn各建一条。)
3.3 一个完整的软链清单
以下是我实际在用的完整清单,涵盖全局层和多个项目层,供参考:
# 真相源(唯一维护点):~/docs/team-docs/skills/{common,order-svc,dw-platform,card-keeper}
# ── 全局层:各工具各挂一条,都指向 common(这一层省不了)──
ln -s ~/docs/team-docs/skills/common ~/.claude/skills # Claude Code
ln -s ~/docs/team-docs/skills/common ~/.codex/skills # Codex
ln -s ~/docs/team-docs/skills/common ~/.agents/skills # Cline / Warp / Zed / Kimi
ln -s ~/docs/team-docs/skills/common ~/.qoder/skills # QoderCN 国际版
ln -s ~/docs/team-docs/skills/common ~/.qoder-cn/skills # QoderCN 国内版
# ── 项目层:每个项目三条即可 ──
# .claude/skills → Claude Code
# .agents/skills → Codex / Cursor / Gemini CLI / Copilot / OpenCode… 一条顶一片
# skills(项目根)→ QoderCN 国际版 + 国内版共用
for p in order-svc dw-platform card-keeper; do
SRC=~/docs/team-docs/skills/$p
mkdir -p ~/repos/$p/.claude ~/repos/$p/.agents
ln -s "$SRC" ~/repos/$p/.claude/skills
ln -s "$SRC" ~/repos/$p/.agents/skills
ln -s "$SRC" ~/repos/$p/skills
done
⚠️
ln -s前必须确保目标位置不存在。如果目标已经是个目录(哪怕是空的),ln -s不会替换它,而是在它里面建一个同名软链,变成skills/skills——而且不报错。建完一律ls -l看一眼箭头,别只看命令没报错就以为成了。
四、.agents/skills:正在收敛的中立标准
写这篇的时候我发现一件事,值得单独说:这个「每家一个目录」的乱局,正在被一个中立标准收拾。
Agent Skills 规范 定义了一个不属于任何厂商的目录——.agents/skills/。摘一段各工具的实际映射(完整表在 vercel-labs/skills):
| 工具 | 项目级 Skill 目录 | 全局 Skill 目录 |
|---|---|---|
| Codex | .agents/skills/ |
~/.codex/skills/ |
| Cursor | .agents/skills/ |
~/.cursor/skills/ |
| Gemini CLI | .agents/skills/ |
~/.gemini/skills/ |
| GitHub Copilot | .agents/skills/ |
~/.copilot/skills/ |
| OpenCode | .agents/skills/ |
~/.config/opencode/skills/ |
| Cline / Warp / Zed / Kimi Code CLI | .agents/skills/ |
~/.agents/skills/ |
| Claude Code | .claude/skills/ |
~/.claude/skills/ |
| QoderCN 国际版 / 国内版 | .qoder/skills/(⚠️ 实测不生效,见 3.2) |
~/.qoder/skills/、~/.qoder-cn/skills/ |
盯着这张表看,会发现一个很有意思的分裂:
- 项目层已经基本统一了:Codex、Cursor、Gemini CLI、Copilot、OpenCode、Cline、Warp、Zed……项目级清一色
.agents/skills/。 - 全局层还各玩各的:同样这批工具,全局路径五花八门(
~/.codex/、~/.cursor/、~/.gemini/、~/.copilot/、~/.config/opencode/),只有 Cline、Warp、Zed、Kimi Code CLI 这一组真正落到了中立的~/.agents/skills/。
收敛是从项目层开始的——这跟直觉相反(你会以为全局配置更容易统一),但想想也合理:项目目录是团队共享的,一个仓库里塞七八个 .xxx/skills 谁都受不了;而全局目录是个人机器上的事,各家没动力改。
对本文方案的实际影响:
# 项目层:一条 .agents/skills 顶一大片工具,优先建这条
mkdir -p ~/repos/order-svc/.agents && \
ln -s ~/docs/team-docs/skills/order-svc ~/repos/order-svc/.agents/skills
# 全局层:中立目录只覆盖 Cline / Warp / Zed / Kimi 这一组,其余仍需各建各的
ln -s ~/docs/team-docs/skills/common ~/.agents/skills
⚠️ 别把
~/.agents/skills当成全局层的万能钥匙。 我一开始就理解错了,以为建了它 Codex 全局就通了——实际 Codex 全局读的是~/.codex/skills/,那条还得单独建。中立目录在项目层是「一条顶一片」,在全局层只是「多一个工具组」。
顺带一提,既然有了统一规范,就有了配套的包管理器:skills CLI 能直接从 GitHub 装社区写好的 Skill,装完各工具通用。这也带来一个坑,放在第七节讲。
五、.gitignore 怎么处理软链
软链建好之后,有个容易踩的坑:Git 会把软链本身当作一个文件来跟踪(存储的是链接目标路径字符串),而不是跟踪它指向的目录内容。
如果你不想把软链提交到仓库(比如它是本地开发环境的私有配置),需要在 .gitignore 中排除它。
5.1 软链能被 .gitignore 排除吗?
能。 Git 对软链的处理是把它当作一个普通的 blob 文件,.gitignore 的规则可以正常匹配。
# 排除项目级软链(项目根的 skills;若历史上还留着 .codex/.qoder/.qoder-cn 旧链一并排除)
/skills
.codex/skills
.qoder/skills
.qoder-cn/skills
5.2 如果软链已经被 Git 跟踪了怎么办?
需要先从索引中移除,再加 .gitignore:
# 从索引中移除(不删除物理文件)
git rm --cached skills
# 然后加 .gitignore
echo "/skills" >> .gitignore
💡
git rm --cached只从 Git 索引中移除,不会删除磁盘上的软链。放心用。
5.3 另一种思路:把软链也提交进去
如果你的团队都使用相同的工具组合,也可以把软链提交到仓库里。这样其他人 git clone 下来就自动有了正确的软链结构。不过要注意:
- 软链的目标路径必须是相对路径(否则换个人机器上就断了)
- 或者用脚本在初始化时动态创建(见下一节)
六、新增 Skill 的完整流程
有了软链之后,新增一条 Skill 的流程变得非常简单:
- 判断归属:这条 Skill 是全局通用的,还是项目专属的?
- 在物理目录创建:
mkdir -p {物理目录}/{skill-name} - 编写 SKILL.md:frontmatter 填
name和description,正文写规则 - 完成——四个工具自动生效,不需要额外操作
6.1 SKILL.md 的编写注意
由于多个工具共享同一份 SKILL.md,description 中应使用通用表述,不要写死某个工具的名字:
# 推荐 ✅
description: |
TRIGGER when 执行 mvn / gradle / javac 编译时...
# 不推荐 ❌
description: |
让 Claude 在执行 mvn 编译时自动...
这样无论哪个工具加载这条 Skill,描述都是准确的。
6.2 软链完整性检查
真正容易漏的是全局层——每个工具一条、路径还各不相同,漏建一条你只会发现"某个工具没加载规则",但很难第一时间想到是软链没建。项目层反而省心:三条固定写法,其中 .agents/skills 一条覆盖一大批。
一个自检脚本,全局层逐条查、项目层查三个位置:
check() { # $1=路径
if [ -L "$1" ]; then
tgt=$(readlink "$1")
[ -e "$1" ] && echo "✅ $1 → $tgt" || echo "💀 死链: $1 → $tgt(目标不存在)"
elif [ -d "$1" ]; then
echo "⚠️ $1 是真实目录,不是软链(八成是 ln -s 嵌套了,查查里面有没有 skills/skills)"
else
echo "❌ 缺: $1"
fi
}
# 全局层:各工具路径不同,一条都不能少
for l in ~/.claude/skills ~/.codex/skills ~/.agents/skills ~/.qoder/skills ~/.qoder-cn/skills; do
check "$l"
done
# 项目层:每个项目三条
for dir in ~/repos/*/; do
d="${dir%/}"
check "$d/.claude/skills" # Claude Code
check "$d/.agents/skills" # Codex / Cursor / Gemini CLI / Copilot…
check "$d/skills" # QoderCN(项目根)
done
比原来多了两种状态判断:死链(软链在、目标没了,
readlink照样有输出,容易误判成正常)和真实目录(多半是ln -s嵌套的后果,见 7.1)。只判断-L会把这两种都放过去。
七、踩坑记录
7.1 ln -s 嵌套创建
现象:软链不生效,ls -la 发现 ~/repos/order-svc/skills 变成了一个真实目录,里面还嵌了一个叫 skills 的软链。
原因:建软链时 ~/repos/order-svc/skills 已经存在(可能是个空目录),ln -s 不会覆盖,而是在这个目录里面创建了一个名为 skills 的软链。
解决:先删掉已有的空目录,再建软链:
rm -rf ~/repos/order-svc/skills # 确认是空目录 / 失效软链再删!
ln -s ~/docs/team-docs/skills/order-svc ~/repos/order-svc/skills
7.2 软链目标路径用绝对路径 vs 相对路径
绝对路径的好处是直观、不容易搞错层级;坏处是换台机器或者目录搬家就断了。
相对路径的好处是仓库可以整体迁移;坏处是层级关系搞错了就 404。
我的选择:全局层用绝对路径(因为 ~ 展开后每个人的路径本来就不同,全局层本来就是本地配置),项目层也用绝对路径(配合脚本初始化,不依赖相对层级)。
如果你希望仓库可移植,项目层建议用相对路径:
# 相对路径写法(项目根 skills)
ln -s ../../docs/team-docs/skills/order-svc skills
7.3 删除物理目录后软链变"死链"
现象:某个工具突然不加载 Skill 了,ls -la 发现软链指向的目标不存在了。
原因:物理目录被删除或重命名了,但软链没有同步更新。
排查:
# 找到所有死链
find . -type l ! -exec test -e {} \; -print
解决:重新指向正确的物理目录,或者删除死链。
7.4 装社区 Skill,装进了自己的真相源仓库
有了统一规范就有了包管理器:skills CLI 能从 GitHub 拉别人写好的 Skill 装到本地。我顺手装了两个,然后 git status 一看愣住了——它们出现在我的真相源仓库里,还被 git 跟踪了。
原因:~/.agents/skills 是一条指向真相源的软链。CLI 往「~/.agents/skills/」写文件,写的其实是软链背后那个物理目录——也就是你的仓库。软链是双向透传的,你从软链读,别人也能从软链写进去。
这不是 bug,得看你要什么:
- 想要:三方 Skill 一并版本化、多工具通用、换机器 clone 就有 → 保持现状,但在 README 里标出哪些是三方的,免得半年后看见没印象的目录顺手删了。
- 不想要:那就别把中立目录指向仓库,改成物理目录 + 仓库软链进去(反向挂),或者干脆
.gitignore掉三方那几个目录。
还有一条:三方 Skill 的安装台账在 ~/.agents/.skill-lock.json(记来源仓库、SKILL.md 路径、内容哈希)。要卸载走 CLI,别手动 rm -rf 目录——删了目录但台账还在,下次 CLI 检查状态就对不上了。
💡 顺带说:这也是「只软链
skills/这一层、别整个.agents/链过去」的另一个理由——.skill-lock.json这种工具自己管的状态文件,本来就不该进你的仓库。
八、方案对比:软链 vs 其他方案
可能有人会问:为什么不直接用其他方案?简单对比一下:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 软链(本文方案) | 零依赖、即时生效、改一处全生效 | 需要理解软链机制;IDE 偶尔不跟随软链 |
| 复制文件 | 最简单粗暴 | 改一条改 N 处,必然漂移 |
| 脚本同步 | 可以跨机器 | 需要定时任务或手动触发,有延迟 |
| 统一配置目录 | 最优雅 | 需要工具本身支持自定义配置路径(目前不支持) |
软链方案的核心优势是零维护成本——建好之后就不用管了,新增/修改/删除 Skill 都是操作物理目录,软链自动透传。
总结
四个 AI 编码工具,四份配置目录,一份 Skill 内容——用一条 ln -s 就能把它们串起来,告别复制粘贴和配置漂移。
核心就四句话:
- 物理目录只存一份 SKILL.md,这是唯一的维护点
- 全局层每个工具各建一条软链,路径互不相同、一条都省不了(
~/.claude/skills、~/.codex/skills、~/.agents/skills、~/.qoder/skills、~/.qoder-cn/skills) - 项目层只要三条:
.claude/skills给 Claude Code、.agents/skills一条顶一片(Codex / Cursor / Gemini CLI / Copilot / OpenCode…)、项目根skills/给 QoderCN 两个版本共用 .gitignore可以正常排除软链,不想提交就加一行规则
如果只让我留一句话,是这个:「全局路径去掉 ~ 就是项目路径」这个直觉是错的。Codex 全局在 ~/.codex/skills、项目级却在 .agents/skills;QoderCN 全局在 ~/.qoder/skills、项目级却在项目根 skills/。这两处不对称,是我在这套方案上浪费时间最多的地方——软链本身五秒就建好了,难的是知道该往哪儿建。
如果你也在多工具之间反复同步配置,不妨试试这个方案。毕竟,改一条规则只需要改一个地方,才是工程师该有的生活。
延伸阅读
- CLAUDE.md 写到 500 行还管不住 AI?Skills 分层食用指南 + AGENTS.md 跨工具吃遍天下 —— 软链共享的这份配置具体怎么写:Skills 分层 + AGENTS.md 跨工具复用
🏷️ 标签:AI 编码 Claude Code Codex Agent Skills 软链 配置管理
更多推荐

所有评论(0)