Skill discovery recursively registers nested `SKILL.md` files inside symlinked skill directories
What version of Codex CLI is running?
codex-cli 0.130.0
What subscription do you have?
Not relevant.
Which model were you using?
Not relevant.
What platform is your computer?
linux
What terminal emulator and version are you using (if applicable)?
Not relevant.
What issue are you seeing?
Codex CLI appears to scan ~/.agents/skills recursively for every SKILL.md, while also following symlinked skill directories. This causes nested implementation files inside a symlinked skill package to be registered as independent user skills.
Example setup:
~/.agents/skills/workflow-toolkit
-> /tmp/codex-skill-repro/workflow-toolkit
The symlink target has its own root SKILL.md, but it also contains internal source trees:
workflow-toolkit/
SKILL.md
runtime-sources/
app-workflow/
skills/
apply-log/
SKILL.md
init-log/
SKILL.md
writing-workflow/
skills/
apply-log/
SKILL.md
init-log/
SKILL.md
Those nested runtime-sources/**/SKILL.md files are source material for the top-level skill package. They are not intended to be installed user skills.
Codex nevertheless registers them as available skills. This produces duplicate entries in the model-visible skill list, for example:
apply-log app workflow helper
apply-log writing workflow helper
init-log app workflow helper
init-log writing workflow helper
workflow-toolkit intended top-level skill
This makes skill routing ambiguous and noisy. It also makes symlinked skill packages behave differently from the user expectation that one direct child of ~/.agents/skills represents one installed skill.
What steps can reproduce the bug?
Create a minimal skill package with one root skill and nested internal skill-like source files:
mkdir -p /tmp/codex-skill-repro/workflow-toolkit/runtime-sources/app-workflow/skills/apply-log
mkdir -p /tmp/codex-skill-repro/workflow-toolkit/runtime-sources/writing-workflow/skills/apply-log
cat > /tmp/codex-skill-repro/workflow-toolkit/SKILL.md <<'EOF'
---
name: workflow-toolkit
description: Top-level workflow toolkit skill.
---
# Workflow Toolkit
EOF
cat > /tmp/codex-skill-repro/workflow-toolkit/runtime-sources/app-workflow/skills/apply-log/SKILL.md <<'EOF'
---
name: apply-log
description: Internal app workflow source file, not an installed user skill.
---
# Internal App Workflow Source
EOF
cat > /tmp/codex-skill-repro/workflow-toolkit/runtime-sources/writing-workflow/skills/apply-log/SKILL.md <<'EOF'
---
name: apply-log
description: Internal writing workflow source file, not an installed user skill.
---
# Internal Writing Workflow Source
EOF
Symlink that package into ~/.agents/skills:
ln -s /tmp/codex-skill-repro/workflow-toolkit ~/.agents/skills/workflow-toolkit
Start Codex and inspect available skills, or call the app-server skills/list method with forceReload: true.
The filesystem difference is easy to see from the shell. A shallow scan sees no nested files:
find ~/.agents/skills -maxdepth 2 -name SKILL.md -print
But following symlinks recursively reveals files that Codex also registers:
find -L ~/.agents/skills -name SKILL.md -print | rg 'workflow-toolkit|apply-log'
Observed output:
~/.agents/skills/workflow-toolkit/SKILL.md
~/.agents/skills/workflow-toolkit/runtime-sources/app-workflow/skills/apply-log/SKILL.md
~/.agents/skills/workflow-toolkit/runtime-sources/writing-workflow/skills/apply-log/SKILL.md
The app-server skills/list method reports the same nested files as available user skills:
{
"method": "skills/list",
"params": {
"cwds": ["/tmp/codex-skill-repro"],
"forceReload": true
}
}
Representative result:
name: workflow-toolkit
path: /tmp/codex-skill-repro/workflow-toolkit/SKILL.md
enabled: true
name: apply-log
path: /tmp/codex-skill-repro/workflow-toolkit/runtime-sources/app-workflow/skills/apply-log/SKILL.md
enabled: true
name: apply-log
path: /tmp/codex-skill-repro/workflow-toolkit/runtime-sources/writing-workflow/skills/apply-log/SKILL.md
enabled: true
What is the expected behavior?
Codex should treat each direct child of ~/.agents/skills as one installed skill entry:
~/.agents/skills/<skill-name>/SKILL.md
If <skill-name> is a symlink to a directory, Codex should resolve the symlink to read the target's root SKILL.md, but it should not continue recursively discovering additional nested SKILL.md files inside that target.
In other words, the symlink target should be used as the skill directory, not as a new recursive discovery root.
The preferred fix is to prohibit deeper recursive discovery after resolving a direct installed skill directory. Installing one skill folder should install one skill.
Alternative acceptable fixes:
- Add a scan-depth option, defaulting to one skill directory level.
- Add a skill discovery ignore option, such as
skills.discovery.exclude = ["**/runtime-sources/**"]. - If recursive scanning is intentional, document it explicitly in the skills docs, including the fact that any nested
SKILL.mdunder an installed skill directory may be registered as a separate skill.
My recommendation is the first option: do not scan deeper. That keeps symlinked skill directories consistent with normal installed skill directories and avoids accidental registration of internal source material.
Additional information
Current workarounds:
- Disable specific false-positive skills by path in
config.toml:
[[skills.config]]
path = "/tmp/codex-skill-repro/workflow-toolkit/runtime-sources/app-workflow/skills/apply-log/SKILL.md"
enabled = false
This works after restarting Codex, but it does not prevent the scan and does not scale well when many nested SKILL.md files exist.
- Disable by one-off CLI config override:
codex -c 'skills.config=[{path="/tmp/codex-skill-repro/workflow-toolkit/runtime-sources/app-workflow/skills/apply-log/SKILL.md", enabled=false}]'
This causes skills/list to report that entry with:
enabled: false
- Disable individual skills through
/skillsin the TUI. This is only practical for a small number of false positives.
- Avoid symlinking rich development packages into
~/.agents/skills. Instead, use a shallow wrapper directory containing only the intended rootSKILL.md, or install a compiled flat skill snapshot that has only direct skill directories.
The last workaround avoids the issue, but it requires users to restructure otherwise valid skill packages. The better default behavior would be for Codex to stop discovery at the installed skill directory boundary.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗