Skip to content

fix: resolve skills across all three install roots, not ~/.claude/skills alone (1.4.0) - #27

Merged
wan-huiyan merged 1 commit into
mainfrom
fix/plugin-install-path-resolution
Aug 6, 2026
Merged

fix: resolve skills across all three install roots, not ~/.claude/skills alone (1.4.0)#27
wan-huiyan merged 1 commit into
mainfrom
fix/plugin-install-path-resolution

Conversation

@wan-huiyan

Copy link
Copy Markdown
Owner

What this fixes

A skill installed as a plugin lives at ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/, not at ~/.claude/skills/<name>/. Anything that reaches a skill through the ~/.claude/skills/ root alone misses on a plugin install, and the usual fallback is a log line plus "continue" — so the step silently does nothing while the summary still reads clean.

CLAUDE_PLUGIN_ROOT does not rescue this. It is frequently unset in the shell a step actually runs in, and it points at the running plugin's own root, so it can never reach a sibling plugin.

Same bug class as session-handoff v1.17.0 (wan-huiyan/session-handoff#13).

1. The executable lookup — overnight-insight-discovery (this one had teeth)

references/ccr_env_toolchain_preflight.md decided whether this skill was installed with a single line:

test -f ~/.claude/skills/overnight-insight-discovery/SKILL.md

On a plugin install that test fails. The documented failure path is not benign: the track writes env_preflight.json with "PASS": false, commits, and taps out with [ENV_BLOCKER] reporting no skill tree. A failed lookup gets reported as an install-state finding — exactly the misread that motivated the session-handoff fix.

It now probes all three roots in order and, on a miss, names the paths it tried:

S="${CLAUDE_PLUGIN_ROOT:+${CLAUDE_PLUGIN_ROOT}/SKILL.md}"
[ -f "$S" ] || S="$HOME/.claude/skills/overnight-insight-discovery/SKILL.md"
[ -f "$S" ] || S="$(find -L "$HOME/.claude/plugins/cache" -mindepth 4 -maxdepth 4 \
    -path '*/overnight-insight-discovery/*/SKILL.md' 2>/dev/null \
  | awk -F/ '{print $(NF-1)"\t"$0}' | sort -V -k1,1 | tail -1 | cut -f2-)"
if [ -f "$S" ]; then
  echo "overnight-insight-discovery SKILL.md: $S"
else
  echo "overnight-insight-discovery SKILL.md: not found — tried \$CLAUDE_PLUGIN_ROOT/, ~/.claude/skills/overnight-insight-discovery/, and ~/.claude/plugins/cache"
fi

Three details are not cosmetic:

  • Ranks on the version segment alone ($(NF-1), depth 4 — the target here is <version>/SKILL.md, not <version>/scripts/<script>). The marketplace segment precedes the version in the path, so a plain sort -V over whole paths ranks by marketplace name and lets aaa-mkt/2.5.0 lose to zzz-mkt/1.0.0.
  • find, not a shell glob. zsh's nomatch fails a non-matching glob at expansion time, before 2>/dev/null can apply, printing a raw shell error.
  • "not found — tried <paths>", never a bare "not installed". A failed lookup is not evidence about install state.

Tested against a fake $HOME with two marketplaces (zzz-mkt/1.0.0 and aaa-mkt/1.10.0): picks aaa-mkt/1.10.0; silent and correct with no cache directory at all under zsh; CLAUDE_PLUGIN_ROOT wins when set.

Nothing here redirects to a file, so the guard-before-redirecting rule has no site to apply to in this change.

2. The dead documentation links — schedule-poll-orchestrator-pattern

Three see-also entries were markdown links of the form [name](~/.claude/skills/name/SKILL.md) — a file a reader on a plugin install cannot open. There is no local path that resolves under every install method, so they are now plain skill names, with a GitHub URL where the source repo is known:

Also removed one ~/.claude/skills/-rooted self-reference from overnight-insight-discovery's v1.3.2 changelog entry (it now says "this skill's own references/phase_b_review_loop.md").

Deliberately NOT changed

Every ~/.claude/skills/** mention in § "Autonomous-safe skill edits" and the Phase G summary above it — SKILL.md lines 570, 596, 606, 610 and 810. Those are the path patterns that fire a sensitive-file permission prompt in Claude Code. That is a fact about the prompt system, not about where this skill lives; broadening them to include the plugin cache would change what the contract forbids, and narrowing them would break it outright. Confirmed present at the remote tip after the push.

Also left alone, and worth a separate decision rather than a silent edit:

  • README.md lines 96–98 (cp -R … ~/.claude/skills/) and plugins/observational-analysis-rigor/skills/data-provenance-verifier/README.md:33 (git clone … ~/.claude/skills/…) — install instructions that create that root. Correct as written.
  • references/phase_c_consolidation.md:225, "Commit any skill changes under the user's ~/.claude/skills/ directory." This one does assume a single root, but it is plain guidance to a human about their own skills directory, and the Phase G autonomous-safe contract already overrides it for unattended runs. Rewriting it means deciding what "commit a skill change" even means on a plugin install (you would be editing a cache directory), which is a design question, not a path fix.

Versions

before after
bundle VERSION 1.3.1 1.4.0
overnight-insight-discoveryplugin.json + marketplace.json 1.1.1 1.2.0
overnight-insight-discovery — SKILL frontmatter 1.8.0 1.9.0
schedule-poll-orchestrator-patternplugin.json + marketplace.json 1.0.0 1.0.1
schedule-poll-orchestrator-pattern — SKILL frontmatter 1.0.0 1.0.1

Minor for overnight-insight-discovery because the pre-flight check's behaviour changes; patch for schedule-poll-orchestrator-pattern because it is links only.

Pre-existing drift, not introduced here and not reconciled here: overnight-insight-discovery's SKILL frontmatter (1.8.0) and its manifests (1.1.1) are on two different numbering lineages, and have been for several releases. I bumped each within its own lineage by the same increment rather than guess which is authoritative. overnight-review-panel-blocked-reviewer-reads-as-clean (frontmatter 1.0.0 / manifest 1.0.1) and subagent-review-tier-calibration-for-overnight-pr-chains (frontmatter 1.0.1 / manifest 1.0.0) have the same kind of drift and are untouched by this PR.

README Version history has an entry. Changelogs added to both SKILL.md files (schedule-poll-orchestrator-pattern had no version-history section before; it has one now).

Checks

All three CI gates were run locally before the change (all green) and again after (all green): validate_plugins.py, leak_scan.sh, check_skill_descriptions.py --triggers. Nothing was red before this change.

Do not auto-merge.

…lls alone (1.4.0)

A skill installed as a plugin lives at
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/, not at
~/.claude/skills/<name>/. Anything that reaches a skill through the
~/.claude/skills/ root alone misses on a plugin install.

The one with teeth — overnight-insight-discovery's Phase 0.Y toolchain
pre-flight decided whether the skill was installed with a single
`test -f ~/.claude/skills/overnight-insight-discovery/SKILL.md`. On a plugin
install that test fails, and the failure path is a tap-out with [ENV_BLOCKER]
reporting no skill tree: a failed lookup reported as an install-state finding.
It now probes $CLAUDE_PLUGIN_ROOT, then ~/.claude/skills/, then the plugin
cache, and on a miss prints "not found - tried <the three paths>".

Three details in the snippet each fix a real defect and should not be
simplified away: it ranks cache hits on the version path segment alone
(the marketplace segment sorts first, so a plain `sort -V` over whole paths
would let aaa-mkt/2.5.0 lose to zzz-mkt/1.0.0), it uses find rather than a
shell glob (zsh's nomatch fails a non-matching glob at expansion time, before
2>/dev/null can apply), and the miss message names the paths tried instead of
claiming "not installed".

Dead documentation links — three see-also entries in
schedule-poll-orchestrator-pattern pointed at ~/.claude/skills/<name>/SKILL.md
files a reader on a plugin install cannot open. No local path resolves under
every install method, so they are now plain skill names with a GitHub URL
where the source repo is known. Also removed one ~/.claude/skills/-rooted
self-reference from overnight-insight-discovery's v1.3.2 changelog entry.

Deliberately unchanged: every ~/.claude/skills/** mention in
§ "Autonomous-safe skill edits" and its Phase G summary (SKILL.md 570, 596,
606, 610, 810). Those are the path patterns that fire a sensitive-file
permission prompt in Claude Code - a fact about the prompt system, not about
where this skill is installed. Broadening them would break the contract.

CLAUDE_PLUGIN_ROOT alone is not the fix: it is frequently unset in the shell
a step actually runs in, and it points at the running plugin's own root, so it
can never reach a sibling plugin.

Versions: overnight-insight-discovery 1.1.1 -> 1.2.0 (manifests) and
1.8.0 -> 1.9.0 (SKILL frontmatter), schedule-poll-orchestrator-pattern
1.0.0 -> 1.0.1 (manifests + frontmatter), bundle VERSION 1.3.1 -> 1.4.0.
Minor because the pre-flight check's behaviour changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wan-huiyan
wan-huiyan merged commit 57af428 into main Aug 6, 2026
1 check passed
@wan-huiyan
wan-huiyan deleted the fix/plugin-install-path-resolution branch August 6, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant