Skip to content

sleep: plugin skills never resolve, because Claude Code records them as plugin:skill #316

Open

Description

Summary

Claude Code records a plugin skill's Skill call with the plugin-qualified name, for example {"skill": "toolbox:doc-truth"}. harvest._skill_names_from_content keeps the name as written (test_sleep_skill_harvest.py pins Example-Skill:v2), so the fan-out looks for a skill called toolbox:doc-truth.

resolve_skill then looks for a directory with that literal name in every root. The plugin roots that skill_search_roots discovers (<cache>/<marketplace>/<plugin>/<version>/skills, from #185 and 109bafb) contain doc-truth/, so a plugin skill from a real transcript always comes back missing. The resolver tests use bare names in plugin roots (chrome-devtools under chrome-devtools-mcp/<version>/skills), which is not what Claude Code writes.

Repro

On main at 343db22, with a claude_home holding plugins/cache/jay-skills/toolbox/0.6.4/skills/doc-truth/SKILL.md:

roots = skill_search_roots(cfg)                  # includes .../toolbox/0.6.4/skills
resolve_skill("toolbox:doc-truth", roots).status  # 'missing'
resolve_skill("doc-truth", roots).status          # 'found'
_skill_names_from_content([{"type": "tool_use", "name": "Skill",
                            "input": {"skill": "toolbox:doc-truth"}}])  # ['toolbox:doc-truth']

End to end on 79124b3 (skill_resolver.py is unchanged since), a dry-run --source claude with multi_skill_fanout on a real session reported the group as skipped, "no SKILL.md for this skill in the configured skill roots". --skill-root can't work around it, because no directory has a colon in its name.

On one machine's Claude Code history: 264 Skill calls, 138 of them plugin-qualified, and 129 of those have <skill>/SKILL.md in the root the resolver picks for that plugin. None of the 138 resolve today.

Why it needs more than a resolver change

If the name did resolve, staging would still refuse it. staging._safe_skill_name rejects : because the name becomes proposed_SKILL.<name>.md and Windows can't store it. skill_proposal_rows and _adopt_live_target_ok also require the live file's parent directory to equal the skill name. So the fix depends on what name a plugin skill is staged and adopted under, and I'd rather you pick that.

Options

  1. Resolve plugin:skill in that plugin's root only, and stage under the directory name. Return the plugin roots keyed by plugin, send a qualified name only to its plugin's root, and send a bare name to every root as today. Reports keep toolbox:doc-truth; the proposal is staged and adopted as doc-truth (adopt --skill doc-truth). If two plugins' doc-truth show up in one night, skill_proposal_rows already refuses the duplicate. The resolver still never guesses between roots.
  2. Same resolution, staged under an encoded name such as toolbox--doc-truth, so adopt --skill names stay unique across plugins. skill_proposal_rows and _adopt_live_target_ok would need a new rule, since today they tie the staged name to the directory name.
  3. Strip the prefix at harvest and resolve the bare name everywhere. This is the smallest change, but a bare name can then match an unrelated skill. If a plugin's copy isn't in the cache, engineering:code-review would resolve to ~/.claude/skills/code-review and the proposal would be staged against that file. I wouldn't do this one.

I lean to option 1. Happy to send a PR with tests once you've picked the naming: a qualified name resolves only in its plugin's root, a same-named skill elsewhere is ignored, staging and adopt --skill work under the bare name, and duplicate bare names across plugins are still refused.

Activity

  1. Yif-Yang commented on Nov 4, 2026

    @Yif-Yang
    Contributor

    Thanks for the precise diagnosis. #339 proposes a fix: it resolves plugin:skill names only within the matching plugin cache root, and stages proposals under the live directory name. It is in our review queue, and we'll update here when it lands.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions