Symlinked standalone skills inherit plugin namespace from target ancestors

Open 💬 1 comment Opened Aug 22, 2026 by nanozuki

What version of Codex is running?

0.147.0

What platform is your computer?

Darwin 25.5.0 arm64 arm

What issue are you seeing?

A standalone skill installed as a directory symlink under the user skills directory inherits a plugin namespace from an ancestor of the symlink target.

The skill is not installed as part of a Codex plugin, and codex plugin list does not list that plugin. Nevertheless, Codex displays the skill as:

foreign-plugin:demo

instead of:

demo

This affects declarative package managers such as Nix/Home Manager, which normally install files by linking them from an immutable store.

Steps to reproduce

Create this source tree:

source/
├── .claude-plugin/
│   └── plugin.json
└── skills/
    └── demo/
        └── SKILL.md

.claude-plugin/plugin.json:

{
  "name": "foreign-plugin"
}

skills/demo/SKILL.md:

---
name: demo
description: Demonstrate symlinked skill discovery.
---

Then symlink only the individual skill into the Codex user skills directory:

$CODEX_HOME/skills/demo -> source/skills/demo

Restart Codex and inspect the skill selector or run:

codex debug prompt-input "diagnostic"

What did you expect to happen?

Following a symlink for file access should not make an individually installed standalone skill inherit plugin ownership from directories above the symlink target.

Codex should use:

  • the canonical path for reading and deduplication; and
  • the logical discovery path, or explicit plugin installation metadata, for namespace ownership.

The skill should therefore remain demo.

Additional information

Codex canonicalizes the symlinked SKILL.md, treats the canonical target's parent as a namespace root, walks upward outside the configured skills directory, and reads source/.claude-plugin/plugin.json.

Relevant implementation:

  • codex-rs/ext/skills/src/loader/host.rs
  • load_skills_under_root canonicalizes discovered skill paths.
  • When canonicalization changes the path, it adds the target parent to namespace_roots.
  • codex-rs/ext/skills/src/loader/namespace.rs
  • SkillNamespaceResolver walks upward from those roots and applies the nearest manifest name.

The official documentation says Codex supports symlinked skill folders and follows their targets, but it does not document inheriting plugin metadata from ancestors outside the configured skills root:

https://learn.chatgpt.com/codex/build-skills

View original on GitHub ↗

1 Comment

dajiaohuang · 6 days ago

Verified against current main at 343074d4207d572809bd8cea15f4be1d09d98e0b: the same path split is still present. load_skills_under_root canonicalizes the discovered skill for identity, then adds the canonical target's parent to namespace_roots whenever it differs from the logical discovery path. SkillNamespaceResolver subsequently walks upward from that canonical namespace root and can therefore pick up an unrelated ancestor manifest outside the configured standalone-skills root.

A focused regression should keep canonical identity/deduplication intact while distinguishing ownership: an individually symlinked user skill must remain plain even when the canonical target has a plugin-manifest ancestor, while a skill discovered through an actual plugin root must retain that plugin's namespace. This makes the boundary the discovery/ownership metadata passed into namespace resolution, rather than whether the target is readable through its canonical path.