Repository navigation
[Bug]: Same-name skills from different sources collide and can invoke the wrong skill #11161
Description
Activity
Triage
Confirmed on current
main(c52b8d96e4b34201f19b5e5bb12c6b2a77bfaa9a). Distinct skills that share anamebut come from different sources are not independently selectable, and the selected source/path is not preserved through dispatch.This is not a hardcoded preference for official plugins. First matching name wins, so
[plugin, personal]keeps the plugin and[personal, plugin]keeps the personal skill — matching the reporter’s Node check.What the code does today
-
Picker collapses by name.
dedupeProviderSkillsByNameinpackages/client-runtime/src/providerSkills.tskeeps the first trimmed, case-insensitive name and ignorespath/scope. The same helper feeds the web$search (apps/web/src/providerSkillSearch.ts), the slash-menu skill list, and the mobile composer menu. -
Selection is name-only. Web (
ChatComposer.tsx) and mobile insert$${item.skill.name}. Menu row IDs areskill:${provider}:${skill.name}, which is why fix(clients): dedupe skills in composer menus #8043 collapsed duplicates to restore unique highlight/scroll targets. -
Send does not bind a path. Codex
buildTurnStartParams(CodexSessionRuntime.ts) sends the prompt as{ type: "text" }only. There is no structured{ type: "skill", name, path }input. Claude dispatch (ClaudeSkillDispatch.ts) is also name-set based.
Source badges already exist via
resolveProviderSkillSourceKind, and Codexskills/listalready returnspath+scope(kept onServerProviderSkill). The identity is dropped in the picker and never rebound on send.Related, not duplicates
- fix(clients): dedupe skills in composer menus #8043 (merged) fixed duplicate menu IDs by assuming same-name definitions are interchangeable. That holds for the same skill mirrored in
.codexand.agents; it does not hold for unrelated personal vs plugin skills that happen to share a name. - [Bug]: Explicit Codex $skill invocation does not attach user-invoked skills #6095 is the broader “explicit
$skillis not bound as a Codex skill input” gap. Closed PRs fix(server): attach explicit Codex $skill invocations #7196 / fix(codex): send selected skills as structured turn input #9290 tried send-timeSkillUserInput. Even if Codex now loads$namefrom text (as discussed on [Bug]: Explicit Codex $skill invocation does not attach user-invoked skills #6095), two sources with the same name still cannot be chosen independently.
Suggested fix
- Deduplicate by path (or name + source), not name alone. Keep true same-path duplicates collapsed so fix(clients): dedupe skills in composer menus #8043’s highlight/scroll fix stays intact.
- Give menu rows a stable unique id that includes path.
- Preserve the selected path through send (Codex structured skill input with
name+path; Claude needs an equivalent if same-name collisions exist there). - Keep source badges so the two rows are distinguishable.
Workaround until then: rename one skill (as the reporter did:
standards-and-spec-review).Type: bug (medium). Action: keep open; accepted for a path-aware identity + dispatch fix.
Reacted by Florent Klein-
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.acceptedfeature request acceptedfeature request acceptedvia-triageFiled through npx t3 triageFiled through npx t3 triage
on Sep 11, 2026 - added 6 commits that reference this issue
on Oct 7, 2026
Problem
Two unrelated skills named
code-reviewcollide in T3 Code: Matt Pocock's locally installed skill (~/.agents/skills/code-review/SKILL.md) and an official plugin's skill with the same name. Selectingcode-reviewinvoked the plugin skill instead of Matt's. We worked around it by renaming Matt's skill tostandards-and-spec-review.The same happens with one skill copied into two roots, such as
~/.codex/skillsand~/.agents/skills.Reproduction
namebut different files and instructions.$skill picker (or/with skills shown).Expected
Each skill file is its own row in the picker, and the skill you pick is the one that runs.
Actual
Still reproducible on
mainatcd41c4ada0, after the provider V2 rewrite:packages/client-runtime/src/providerSkills.ts:33:dedupeProviderSkillsByNamekeeps only the first skill per name, so the second file has no row.apps/web/src/components/chat/ChatComposer.tsx:4015andapps/mobile/src/features/threads/use-composer-command-menu.ts:160: a pick inserts a bare$name.apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts:3005: the prompt reaches Codex as plain text.Root cause
Codex skips a plain
$namewhen more than one enabled skill has that name (collect_explicit_skill_mentionsincodex-rs/skills/src/selection.rsrequires a name count of 1). The selected skill is never injected, and the model guesses whichSKILL.mdto read.Codex binds a linked mention,
[$name](/path/to/SKILL.md), to that exact file. Checked withcodex-cli 0.160.1and twodupskills in nested.agents/skillsroots, each telling the model to answer with a different word:[$dup](…/sub/.agents/skills/dup/SKILL.md) goWATERMELON(sub copy, injected)[$dup](…/.agents/skills/dup/SKILL.md) goPINEAPPLE(root copy, injected)$dup goGet-Contenton oneSKILL.mdit choseProvider scope
This applies to every supported harness, not only Codex:
/nameeven when the project has its own.The fix must list each file and make each harness run the picked file, not the one its name resolution prefers.
Related