# Claude Plugins in Workflows > Declare Claude Code plugins per workflow and per phase so the agent inside each workspace sees exactly the skills it needs. **Use [Skills](/docs/guide/skills) instead.** Workflow-declared Claude plugins work only for Claude: `--plugin-dir` is a Claude CLI flag, so a Codex phase receives nothing. Skills are the harness-agnostic replacement and are what `syn workflow install` registers automatically. This page is kept for reference. The hooks and guardrails you rely on are unaffected either way: those are baked into the workspace image as platform plugins, not declared per workflow. See [the two-layer model](/docs/guide/skills#two-layers-and-why-it-matters). Workflows can declare a curated set of Claude Code plugins that the agent running each phase should see. The platform fetches the plugin source once, content-addresses the result in object storage, and (in a follow-up release) materializes the resolved plugin set into each workspace at setup time. This page covers the workflow author's view: the YAML field, the CLI commands, error modes, and reproducibility guarantees. Implementation detail lives in [ADR-065](https://github.com/syntropic137/syntropic137/blob/main/docs/adrs/ADR-065-claude-plugin-injection.md) and the gating issue is [#726](https://github.com/syntropic137/syntropic137/issues/726). ## Why this exists Without workflow-scoped plugins, every workspace sees only the four plugins baked into the base image (`sdlc`, `workspace`, `observability`, `git`). New plugins ship through a dogfooded loop: the leverage-points review workflow needs `software-leverage-points`; future workflows will declare their own plugin dependencies. Shipping the dependency alongside the workflow definition makes the workflow self-contained. ## YAML schema Two new fields, both optional and additive: - `claude_plugins:` at the workflow root - every phase sees these plugins. - `claude_plugins:` on a phase - additive on top of the workflow scope. ```yaml id: review-leverage-points name: Leverage Points Review type: review claude_plugins: - syntropic137/software-leverage-points@5.0.7 phases: - id: synthesize name: Synthesize order: 1 prompt_template: "Review the diff against software-leverage-points." claude_plugins: - obra/superpowers@2.1.0 ``` ### Reference forms A `claude_plugins:` entry accepts three input forms; the platform normalizes all three to a single `(source_url, version, name)` lock key. | Form | Example | |---|---| | GitHub shorthand | `syntropic137/software-leverage-points@5.0.7` | | Full URL | `https://gitlab.com/example/extra.git@1.0.0` | | Verbose mapping | `{ source: github.com/obra/superpowers, version: 2.1.0, name: superpowers }` | The verbose form lets you override the display name. Identity is `(source_url, version, name)`: the same plugin declared at workflow and phase scope deduplicates cleanly, while the same source registered under two different names produces two distinct lock entries. ### What is rejected - `@latest` and any unpinned reference. Pinning by tag, branch, or sha is required for reproducibility; the lockfile is the point. - References to private sources without credentials. Authenticated fetch is on the roadmap; today, point at public mirrors. ### Conflict resolution When the same plugin name appears at multiple scopes with different versions, **the innermost scope wins**: phase > workflow > global. Every override is logged at INFO so the resolution is observable. ## CLI The `syn claude-plugin` command group installs new plugins, manages the global registry, and inspects the lock projection. Per ADR-066 the CLI does all `git clone` work locally and POSTs structured payloads to the API; the API container has no git tooling and never shells out. ```bash # Clone the plugin locally and register it with the platform. syn claude-plugin install syntropic137/software-leverage-points@5.0.7 # Same as above, plus enable globally so every workflow sees it. syn claude-plugin install syntropic137/software-leverage-points@5.0.7 --global # Promote an already-registered (name, version) pair into the global set. syn claude-plugin global add software-leverage-points 5.0.7 # List active globals. syn claude-plugin global list # Remove a global registration. The lock entry stays in place so any # workflow that pinned the same (source_url, version, name) keeps resolving. syn claude-plugin global remove software-leverage-points # List every entry in the lock projection (workflow-fetched and global). syn claude-plugin list # Show the lock detail for a specific (name, version) pair. syn claude-plugin show software-leverage-points 5.0.7 ``` `syn claude-plugin --help` prints the full subcommand list with examples. > **Note (#726 Phase B):** the `global add` subcommand now takes > ` ` of an already-registered plugin. The previous > `global add ` shortcut has been replaced by > `install --global`, which runs the clone + register + global-enable > sequence in one step. ## Resolution lifecycle 1. **Workflow install.** When you run `syn workflow install `, the CLI parses every workflow YAML it finds, collects `claude_plugins:` references, and runs a pre-flight: each missing reference is cloned locally and registered via `POST /claude-plugins/registrations` BEFORE any workflow is created. References already in the lock are skipped. 2. **Lock hit.** The existing `(resolved_sha, tree_storage_prefix)` is reused; nothing else happens. 3. **Lock miss.** The CLI clones the source at the pinned version, walks the tree, and uploads every file (base64-encoded) inline. The API computes a sha256 over the normalized tree, persists each file to the `claude-plugins` MinIO bucket, and emits a `ClaudePluginRegisteredEvent`. The lock projection picks it up. 4. **Failure.** If any pre-flight step fails, the install aborts BEFORE any workflow is created. There is no partial state. ## Error codes The API returns these error codes when a reference cannot be resolved: | `error_code` | Meaning | |---|---| | `claude_plugin_unreachable` | Source URL did not respond or returned 404 | | `claude_plugin_version_not_found` | Tag, branch, or sha not present in the source | | `claude_plugin_manifest_missing` | Tree has no `.claude-plugin/plugin.json` | | `claude_plugin_manifest_invalid` | `plugin.json` failed to parse | | `claude_plugin_auth_required` | Source needs credentials (not yet supported) | The CLI surfaces these directly so you can fix the YAML and retry without parsing log output. ## Configuration | Env var | Default | Purpose | |---|---|---| | `SYN_STORAGE_CLAUDE_PLUGIN_BUCKET_NAME` | `claude-plugins` | MinIO bucket for materialized plugin trees | | `DEV__WORKFLOW_FAIL_ON_PLUGIN_NOT_REGISTERED` | `true` | When seeding workflows from disk, fail boot on an unregistered plugin reference. Set to `false` in dev/CI to log and skip. | ## Status and roadmap This feature lands in two stages: - **PR1 (this release).** Dormant plumbing. Workflows can declare `claude_plugins:`, the CLI commands work, and the lock projection fills up. Workspaces still see only the baked-in plugin set. - **PR2 (follow-up).** The materialization step. The resolved plugin set is written into `/.syn-plugins//` and the agent is invoked with `--plugin-dir` flags pointing at each one. Behavior change only takes effect when a workflow declares `claude_plugins:` or a global plugin is registered. Org-level and system-level scopes are tracked in [#761](https://github.com/syntropic137/syntropic137/issues/761).