I keep my Claude Code skills in a single repo that doubles as a plugin marketplace. One of those skills, sounds-like-me, audits a script for my voice. It doesn’t carry the voice rules itself. It reads two style cards that live in other skills in the same plugin.
It worked perfectly on my laptop. Anywhere else, it couldn’t find either card.
The culprit was a couple of hardcoded paths. If you’re bundling skills into a plugin, it’s worth getting this right from day one.
What the broken version looked like
Here’s the original Step 0 from sounds-like-me/SKILL.md:
Read them at these stable paths:
~/.claude/skills/youtube-script/references/style-card.md
~/.claude/skills/wnd-script/references/wnd-series-card.md
If a path is missing, fall back to `find ~/repositories/claude-skills -name "style-card.md"`.
Those paths only existed because I’d symlinked my skills into ~/.claude/skills/ on my authoring machine, and then completely forgot I’d done that. Install the plugin from the marketplace somewhere else and none of that exists. Neither does ~/repositories/claude-skills. Claude would burn a handful of tool calls hunting for the files and then either give up or audit the script without the rules it’s supposed to enforce.
Where plugin skills actually live
When you install a plugin from a marketplace, Claude Code copies it into a cache:
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/skills/<skill-name>/
Notice the <version> segment. That’s one reason a hardcoded path fails, and it’s not the only one.
~/.claude/skills/...is where personal skills live. A plugin install never puts anything there.- A hardcoded cache path breaks on the next version bump, because the directory name changes.
- A path into your own clone only exists on machines where you cloned the repo to that exact spot.
The fix: ${CLAUDE_SKILL_DIR}
Claude Code substitutes ${CLAUDE_SKILL_DIR} with the directory of the running skill. Because a plugin is copied as a whole, sibling skills sit right next to it. So the fix is one .. away:
They ship in sibling skills of this plugin, so read them relative to this skill's directory:
${CLAUDE_SKILL_DIR}/../youtube-script/references/style-card.md
${CLAUDE_SKILL_DIR}/../wnd-script/references/wnd-series-card.md
That resolves correctly everywhere the plugin loads:
- Installed from the marketplace → the versioned cache directory
- Symlinked into
~/.claude/skills/from a clone → straight into the git repo
The same variable works for scripts and templates bundled with the skill itself:
1. Check dependencies:
bash ${CLAUDE_SKILL_DIR}/scripts/check-deps.sh <scenario>
2. Read the template at `${CLAUDE_SKILL_DIR}/TEMPLATE.md`.
Gotchas
The substitution happens before Claude reads anything
${CLAUDE_SKILL_DIR} isn’t something Claude resolves at runtime. It’s already expanded by the time the skill content reaches the model. I noticed this while drafting this post: I passed the literal string ${CLAUDE_SKILL_DIR} as an argument to one of my skills, and it arrived as a full path. Keep that in mind if you ever need to write about the variable inside a skill.
Siblings only work inside the same plugin
../youtube-script/ works because sounds-like-me and youtube-script both ship in the devrel plugin. If a skill depends on a file in a different plugin, a relative path can’t save you. Move the shared file into the plugin that needs it, or combine the skills.
Skills that write files have a second problem
sounds-like-me doesn’t just read the style cards. It appends new rules to their refinement logs. On a marketplace install, those writes land in the cache, which gets overwritten on the next /plugin update.
Relative paths don’t fix that. What they do give you is a resolved path you can look at. I added a check: if the resolved path is under ~/.claude/plugins/cache, the skill still reads the card, but its report warns that any log entries need to be ported back to the repo.
If the resolved path is under `~/.claude/plugins/cache`, still read it, but warn in
the report that any refinement-log entries written there are lost on the next
`/plugin update` and must be ported to the source repo.
On the machine where I actually write skills, I skip the marketplace install. I symlink each skill from my clone into ~/.claude/skills/, so they load as personal skills and those writes go straight into git:
for skill in ~/repositories/claude-skills/devrel/skills/*/; do
ln -s "${skill%/}" ~/.claude/skills/
done
The sibling paths still work here. ${CLAUDE_SKILL_DIR} becomes ~/.claude/skills/sounds-like-me, and the OS follows that symlink before it applies the ... So ../youtube-script resolves inside the clone, right next to the real sounds-like-me folder.
Output locations aren’t the same thing
Not every absolute path is a bug. My done skill saves session logs to a folder in my Obsidian vault. That’s a destination for user data, not a file bundled with the skill, so ${CLAUDE_SKILL_DIR} is the wrong tool there. The actual rule is narrower. Anything the skill ships with should be referenced relative to the skill.
Find the ones you’ve already written
A quick grep through your plugin catches most of them:
grep -rn -E '~/\.claude|/Users/|plugins/cache' --include=SKILL.md .
Not every hit is a bug. When I run that on my repo now, the only things left are the cache check itself and the Obsidian path in done, and both of those are supposed to be there. Anything else is a path that only works on your machine, so swap it for ${CLAUDE_SKILL_DIR} before you publish, not after someone opens an issue about it.
If you want to see how it all fits together, sounds-like-me and its sibling skills are in my claude-skills repo.
Leave a Reply