-
Notifications
You must be signed in to change notification settings - Fork 1
Plugin Format
An existing Claude Code plugin directory loads here unchanged. This page is a consolidation of what's currently spread across npm/src/plugins.ts's doc comments and CONTRIBUTING.md — the source of truth is the code; this is the map of it.
Implemented from the format's public shape — nothing here is copied from Anthropic's source, and none of it is their code.
An OMNIHARNESS.md skill and a plugin command look similar from the outside — both show up as a callable skill — but they do different things:
OMNIHARNESS.md skill |
Plugin command | |
|---|---|---|
| What it is | a shell command | a prompt |
| What invoking it does | runs the command | puts the body in front of the model as instructions, with $ARGUMENTS filled in |
| Needs the shell | yes | no — it runs nothing itself |
| Risk marking | high-risk | not high-risk on its own; whatever tools it then asks for are gated normally |
Skill.kind tells them apart. Absent means shell, so every existing OMNIHARNESS.md is untouched by any of this.
<plugin>/.claude-plugin/plugin.json { name, description, version, author }
<plugin>/commands/*.md frontmatter + a markdown prompt body
<plugin>/agents/*.md same shape, described as an agent
A directory holding .claude-plugin/marketplace.json is a marketplace: a collection whose plugins entries point at individual plugin directories by a relative source. A source that resolves outside the marketplace is ignored rather than followed.
---
description: Code review a pull request
allowed-tools: Bash(gh pr view:*), Bash(gh pr diff:*), mcp__github__comment
---
Review the PR.Read by a small key/value reader, not a full YAML parser. These files come from the internet, and a parser that can evaluate arbitrary YAML is a poor thing to point at them. allowed-tools splits on commas that sit outside the parentheses of a Bash(...) entry — recorded, not yet enforced.
A command's body may use $ARGUMENTS (the whole invocation argument, trimmed) or positional $1, $2, etc. — one word each, matching the shell convention the format borrows. A token with nothing to fill it becomes empty, not left as literal $2, so the model never sees an unfilled placeholder and mistakes it for instruction.
Searched in order, nearest first:
<workspace>/.claude/plugins
<workspace>/.omniharness/plugins
~/.claude/plugins
A workspace-level plugin wins a name collision with a user-level one. Discovery recurses up to two levels — cloning a plugin repository into .claude/plugins puts a marketplace one level below the search root, which is the ordinary case, not an edge one.
OMNIHARNESS_PLUGIN_PATH overrides the whole list, :-delimited like PATH; setting it empty disables discovery entirely. This exists because the default reaches into the user's home directory — without an override, what an agent can do depends on what happens to be installed on the machine it's running on, which is exactly the kind of thing that makes a test pass on one box and fail on every other one.
Commands are namespaced by plugin: code-review:review, not just review — so two plugins can each ship a command called review without colliding.
Cloning the actual anthropics/claude-code marketplace into .claude/plugins and loading it:
plugins discovered: 41
skills exposed: 63
Including code-review:code-review, whose prompt body starts "Provide a code review for the given pull request..." — read correctly, namespaced correctly, exposed to the model as a prompt-kind skill.
See Security Model for how MCP tool descriptions relate to this — a plugin's allowed-tools frontmatter is recorded but not yet enforced, so a plugin from an untrusted source is trusted the same way an MCP server is: don't run one you don't trust.