Skip to content

[Bug]: Same-name skills from different sources collide and can invoke the wrong skill #11161

Description

@floklein

Problem

Two unrelated skills named code-review collide 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. Selecting code-review invoked the plugin skill instead of Matt's. We worked around it by renaming Matt's skill to standards-and-spec-review.

The same happens with one skill copied into two roots, such as ~/.codex/skills and ~/.agents/skills.

Reproduction

  1. Install two enabled skills with the same name but different files and instructions.
  2. Open the Codex $ skill picker (or / with skills shown).
  3. Try to pick the second skill.

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 main at cd41c4ada0, after the provider V2 rewrite:

  • packages/client-runtime/src/providerSkills.ts:33: dedupeProviderSkillsByName keeps only the first skill per name, so the second file has no row.
  • apps/web/src/components/chat/ChatComposer.tsx:4015 and apps/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 $name when more than one enabled skill has that name (collect_explicit_skill_mentions in codex-rs/skills/src/selection.rs requires a name count of 1). The selected skill is never injected, and the model guesses which SKILL.md to read.

Codex binds a linked mention, [$name](/path/to/SKILL.md), to that exact file. Checked with codex-cli 0.160.1 and two dup skills in nested .agents/skills roots, each telling the model to answer with a different word:

Prompt Result
[$dup](…/sub/.agents/skills/dup/SKILL.md) go WATERMELON (sub copy, injected)
[$dup](…/.agents/skills/dup/SKILL.md) go PINEAPPLE (root copy, injected)
$dup go nothing injected; the model ran Get-Content on one SKILL.md it chose

Provider scope

This applies to every supported harness, not only Codex:

  • T3's Claude, Cursor, Grok and Antigravity discovery keeps only the first skill per name, so a second file with the same name cannot be picked.
  • Every provider invokes skills by name, so even a listed second file would not run. Claude Code, for example, runs the user copy of /name even 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

Activity

  1. juliusmarminge commented on Sep 11, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed on current main (c52b8d96e4b34201f19b5e5bb12c6b2a77bfaa9a). Distinct skills that share a name but 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

    1. Picker collapses by name. dedupeProviderSkillsByName in packages/client-runtime/src/providerSkills.ts keeps the first trimmed, case-insensitive name and ignores path / scope. The same helper feeds the web $ search (apps/web/src/providerSkillSearch.ts), the slash-menu skill list, and the mobile composer menu.

    2. Selection is name-only. Web (ChatComposer.tsx) and mobile insert $${item.skill.name}. Menu row IDs are skill:${provider}:${skill.name}, which is why fix(clients): dedupe skills in composer menus #8043 collapsed duplicates to restore unique highlight/scroll targets.

    3. 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 Codex skills/list already returns path + scope (kept on ServerProviderSkill). The identity is dropped in the picker and never rebound on send.

    Related, not duplicates

    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.

  2. added
    bugSomething is broken or behaving incorrectly.
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    on Sep 11, 2026
  3. added 6 commits that reference this issue on Oct 7, 2026
    5f88f12
    561f5c9
    865d268
    b96c745
    baefd7b
    8585d9d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions