fix: resolve render_treatment_report.py across all three install roots (v2.4.0) - #13
Merged
Merged
Conversation
…s (v2.4.0)
SKILL.md and README told the agent to run
`python3 ~/.claude/skills/context-police/scripts/render_treatment_report.py`.
A `/plugin install` never creates that path -- the script lands under
`~/.claude/plugins/cache/<marketplace>/context-police/<version>/skills/context-police/scripts/`
-- so on the plugin install path the command just failed. The usual response
(log it and continue) means the recap step silently does nothing while the run
still reads clean.
Both call sites now resolve across `$CLAUDE_PLUGIN_ROOT`, `~/.claude/skills/`,
and a version-ranked `find` over the plugin cache, and print
`not found - tried <paths>` rather than a bare "not installed".
CLAUDE_PLUGIN_ROOT alone does not fix this: it is frequently unset in the shell
a step runs in, and it points at the calling plugin's own root so it can never
reach a sibling.
The version is ranked on its own path segment (awk '{print $(NF-4)}'), not the
whole path -- the marketplace segment precedes the version, so a plain `sort -V`
over full paths would let aaa-mkt/2.5.0 lose to zzz-mkt/1.0.0. `find` is used
rather than a glob because zsh's nomatch fails a non-matching glob before
2>/dev/null can apply.
tests/plugin-path-resolution.test.mjs extracts the resolver verbatim out of
SKILL.md and README.md and executes it against fixture caches, so the documented
snippet is the thing under test. Negative controls confirmed: changing $(NF-4)
to $(NF-2) turns the version-ranking tests red, and restoring the single-root
invocation turns the static check red.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
SKILL.md:341and the README's recap section both told the agent to run:python3 ~/.claude/skills/context-police/scripts/render_treatment_report.py ...A
/plugin installnever creates that path. The script lands at:(confirmed against a real install on this machine — four cached versions, all at that layout).
So on the plugin install path the command just fails. The usual response is a log line plus "continue", which means the recap step silently does nothing while the summary still reads clean. This is the same class of defect that was fixed in
session-handoffv1.17.0 (wan-huiyan/session-handoff#13), where it made a user believe an installed skill was absent.CLAUDE_PLUGIN_ROOTdoes not rescue this on its own: it is frequently unset in the shell a step actually runs in, and it points at the calling plugin's own root, so it can never reach a sibling plugin.The fix
Both call sites now resolve across all three install roots before invoking, and guard the call:
Four details, each of which has caused a real defect elsewhere:
$(NF-4)). The marketplace segment precedes the version in the path, so a plainsort -Vover whole paths ranks by marketplace name and letsaaa-mkt/2.5.0lose tozzz-mkt/1.0.0. This repo's depth is 7 (not the usual 5) because the plugin nests its scripts underskills/context-police/, which is why the version sits atNF-4rather thanNF-2.find, not a shell glob. zsh'snomatchfails a non-matching glob at expansion time, before2>/dev/nullcan apply, printing a raw shell error.> "$OUT"redirect here (the script takes--out), but theif [ -f "$S" ]guard is still what keeps an unresolved lookup from producing a truncated artifact.Regression test
New
tests/plugin-path-resolution.test.mjs(zero deps,node --test) extracts the resolver verbatim out ofSKILL.mdandREADME.mdand executes it against fixture caches, so the documented snippet is the thing under test rather than a copy that could drift. Per file it asserts:~/.claude/skills/does not exist at allaaa-marketplace/2.5.0beatszzz-marketplace/1.0.0)2.10.0beats2.9.0)~/.claude/skills/over the cache;CLAUDE_PLUGIN_ROOTwins over both when it holds the scriptCLAUDE_PLUGIN_ROOTcollapses to empty rather than becoming/skills/...Plus static checks on both files: no
python3 ~/.claude/skills/...invocation survives, the not-found message names its paths, and the two rendered surfaces ship an identical resolver.Negative controls run, both confirmed red:
$(NF-4)→$(NF-2)python3 ~/.claude/skills/...lineTest suite
npm test— green before the change (23 pass / 0 fail / 1 skipped) and green after (42 pass / 0 fail / 1 skipped). The skip is the pre-existing "VERSION file (if present)" test; this repo has noVERSIONfile.Version
2.3.0→2.4.0(minor — behavior changes). Synced inplugin.json,.claude-plugin/marketplace.json, and theSKILL.mdfrontmatter.package.jsonhas noversionfield, so nothing to sync there. README Version history has av2.4.0entry. Plugin copy regenerated withscripts/dev/sync_plugin_copy.sh.Deliberately not changed
--skills-dir ~/.claude/skillsstays as-is everywhere, and so do theSK = ~/.claude/skillsdefaults inrender_treatment_report.py,apply_disable_model_invocation.py,pilot/retrieve.py, andpilot/phase8_embeddings_probe.py. These are the audit target — the skills universe being measured — not an executable lookup. (Worth noting separately: on a plugin-heavy install that universe is arguably incomplete, since plugin-supplied skills live in the cache. That is a different bug class and out of scope here.)git clone ... ~/.claude/skills/context-police— that is a manual-install command that creates the directory, not a lookup of an existing one.docs/workstream/**references to~/.claude/skills/...— archived handoff records of what was run at the time; rewriting them would falsify the record.