Packaging as a plugin
This page covers Claude Code plugin packaging from the karpathy-wiki shape: minimal.claude-plugin/plugin.json, skills in skills/<name>/SKILL.md, optional sibling sub-directories, the symlink install pattern, and the ${CLAUDE_PLUGIN_ROOT} substitution gotcha.
Cross-platform packaging (Cursor, OpenCode, Gemini, Codex) lives in docs/11-cross-platform/. This page is Claude Code-specific.
The minimal plugin shape
A Claude Code plugin is a directory with one required file plus skill directories.Plugin manifest: .claude-plugin/plugin.json
The minimal manifest, mirrored from karpathy-wiki’s shape (KP-PLUGIN):
name, version, description, author. Optional but recommended: repository, license, keywords.
The name is the namespace prefix for your plugin’s skills (Claude Code uses plugin-name:skill-name for namespacing). Pick a name that will not collide with other plugins.
The version follows semantic versioning. Note: a description-string change in your skills can break implicit triggering (the description is the activation contract); a description change is conceptually a major-version shift even if the body is unchanged. See docs/09-evolution.md.
Where skills live
Per the convention fromKP-PLUGIN and Claude Code’s docs: plugin skills live at skills/<skill-name>/SKILL.md relative to the plugin root.
Optional sibling sub-directories under each skill:
scripts/. Helper scripts the SKILL.md invokes. The agent-skills spec convention (Layer 1).references/. Heavy reference docs loaded on demand. One level deep from SKILL.md; never nested. Seedocs/05-authoring/line-budget.md.assets/. Data files, fixtures, images. Loaded as needed.
Install paths
Claude Code recognizes plugins via:- Plugin marketplace. Subscribe via
claude plugin marketplace add <url>. Plugins published in a marketplace install withclaude plugin install <name>. - Local clone + symlink. The pattern karpathy-wiki uses for development:
git clonethe plugin repo, thenln -s <plugin-skill-dir> ~/.claude/skills/<name>to make the skill available without going through the plugin system. - Direct copy.
cp -r <plugin-skill-dir> ~/.claude/skills/<name>. Less common; loses the link to upstream.
docs/04-token-economics.md and docs/11-cross-platform/claude-code.md):
enterprise > personal > project > pluginA skill at
~/.claude/skills/wiki/ (personal) shadows a skill at <plugin>/skills/wiki/. Plugin skills are namespaced (plugin-name:wiki) so they cannot shadow each other across plugins, but they CAN be shadowed by personal or project skills with the same bare name.
The ${CLAUDE_PLUGIN_ROOT} gotcha
Source: REVIEWER G3, “wiki concept page references” (/Users/lukaszmaj/wiki/concepts/claude-code-plugin-root-substitution.md).
The literal string ${CLAUDE_PLUGIN_ROOT} appears as a config-time substitution token in plugin.json and hooks.json. Claude Code expands it to the plugin’s installed path when reading those files. The token works inside Claude Code’s own configuration plumbing.
The token does NOT propagate to the Bash tool. A SKILL.md that says:
Exit 127 / No such file or directory when run as a personal skill (the symlink-install case). The literal string ${CLAUDE_PLUGIN_ROOT} reaches Bash, expands to the empty string (Bash sees an unset variable), and the command becomes bash /scripts/foo.sh, which does not exist.
The failure mode is silent during plugin install; it surfaces only when someone installs the skill personally (via symlink) and tries to invoke it.
The three workarounds documented in the wiki concept page:
- Use
${CLAUDE_SKILL_DIR}instead. Per Claude Code docs, this token is “available in bash injection commands to reference scripts or files bundled with the skill, regardless of the current working directory.” It propagates to Bash where${CLAUDE_PLUGIN_ROOT}does not. - Use a relative path.
bash scripts/foo.shworks if the skill’s working directory is set to the skill’s base directory. Some skills docd "$(dirname "$0")"first; some rely on Claude Code settingpwdcorrectly. - Compute the path explicitly. From a SKILL.md prose preamble: “All script paths below are relative to this skill’s base directory (shown at the top of the skill as
Base directory for this skill: ...).cdinto that directory before invoking any script, or prefix each script with the absolute base path.” This is karpathy-wiki’s chosen workaround.
Cross-platform manifests (brief)
Other harnesses use different manifest shapes:- Cursor:
.cursor-plugin/plugin.jsondeclares every artifact path explicitly. - OpenCode:
.opencode/plugins/<name>.jsis a JavaScript plugin that registers the skills directory and prepends bootstrap context to the first user message. - Gemini:
~/.gemini/extensions/<name>/gemini-extension.jsondeclares an extension (not a plugin); always-on context lives inGEMINI.md, custom commands incommands/*.toml. - Codex:
agents/openai.yamlis the Codex-specific sidecar; the skills themselves live in.agents/skills/<name>/.
docs/11-cross-platform/. Most spec-compliant skills work in multiple harnesses with no modification (the SKILL.md is portable); only the manifest layer differs per harness.
Sources
LANDSCAPE2.1 (Claude Code plugin packaging).LANDSCAPE1.4 (cross-platform manifest landscape).REVIEWERG3 (${CLAUDE_PLUGIN_ROOT}config-time-token-vs-shell-env-var gotcha; the three workarounds).KP-PLUGIN(the minimal manifest shape used as exemplar).
docs/11-cross-platform/.