# Plugin sources

Source: https://codeherder.com/docs/plugin-sources/

Sync a team's Claude Code plugin skills, agents and commands into the workspace catalog at one pinned commit, and see what the sync imports and refuses.

A **plugin source** is one plugin of a plugin marketplace, pinned to one commit. A marketplace is a GitHub or GitLab repo that lists plugins. CodeHerder reads the plugin at the commit you name. It copies the plugin’s skills, agents and commands into your catalog. Every session that starts after the sync can use them.

A plugin source is not native plugin loading. CodeHerder does not install the plugin into the harness. It copies instruction content only. For the other route, see [Changing a device’s settings](https://codeherder.com/docs/device-settings/) and [When a task says “Repo config not trusted”](https://codeherder.com/docs/repo-harness-config/).

You need no repo commit and no device setting for a plugin source. A workspace owner or admin decides, the same as for [Skills](https://codeherder.com/docs/skills/).

## Add a plugin source

From the web app: open **Set up → Plugin sources** and press **Add source**. Fill in three fields.

- **Repo** — the marketplace repo. Use `github:owner/repo`, `gitlab:group/repo`, or an `https` URL.
- **Plugin name** — the plugin’s name in the marketplace.
- **Commit** — the full 40-character commit SHA. A branch name, a tag name or a short SHA is refused. A pin never moves by itself.

Press **Preview** before you save. The preview shows each item the commit imports and each item it refuses. It writes nothing. Press **Add source** to save and sync.

From the CLI:

```
ch plugin-source create --repo github:acme/plugins --plugin review-kit --commit <full-sha>
```

`ch plugin-source list` shows every source. `ch plugin-source show <id>` shows one source with its last sync. A source id is a full UUID, not a name.

## Preview and update a pin

A new release of the plugin does not reach your catalog by itself. You move the pin on purpose.

From the web app: open the source and find **Update pin**. Enter the new commit and press **Preview**. The preview lists each item as added, removed, changed or unchanged. A refused item is listed with its reason. CodeHerder clips a long body, so a very large preview can show paths only. Then confirm the pin.

From the CLI:

```
ch plugin-source preview <id> --commit <full-sha> --files
ch plugin-source pin <id> --commit <full-sha>
```

`--files` adds a diff of each changed file. `pin` shows the item diff and asks you to confirm. In CI, add `--yes`.

A pin is guarded. If another admin moved the pin after you opened the source, your pin fails with `plugin_source_pin_stale` and nothing changes. Reload the source and try again.

An update removes an item that the new commit no longer offers. It also removes an item that the new commit now offers in an invalid form.

## Remove a plugin source

Open the source and press **Remove**, or run `ch plugin-source delete <id>`. The items it synced leave the catalog. A session that starts after the removal does not get them. A session that is already running does not change.

The retained versions of each item stay readable, as [Version history and going back](https://codeherder.com/docs/version-history/) describes. The sync history stays too.

## Groups

A group can own a plugin source. Every workspace below the group sees the synced items. [Managing workspaces](https://codeherder.com/docs/workspaces/) explains groups.

In a child workspace, the source shows as inherited. The child can read it. The child cannot preview, pin or remove it. Make those changes in the group.

## What a sync imports

| Kind | Where CodeHerder looks | Notes |
| --- | --- | --- |
| Skill | `skills/<name>/SKILL.md` and its files | Same rules as [Skills](https://codeherder.com/docs/skills/). |
| Agent | `agents/<name>.md` | Stored as one `AGENT.md`. |
| Command | `commands/**/*.md` | A nested path joins with hyphens. `commands/a/b.md` becomes `a-b`. |

The plugin’s own `plugin.json` can name other paths for these. CodeHerder follows them.

A synced item starts **enabled**. You added the source to make the items available. Your workspace can still turn one off.

## What a sync never imports

A catalog item loads into every agent session of the workspace. Executable configuration needs the trust of a device owner, and a plugin source does not ask for it. So the sync refuses these and lists each one in the sync report:

- Hooks.
- MCP servers.
- LSP servers.
- Environment variables.
- Settings.
- Output styles.
- An item whose frontmatter has a `hooks`, `mcpServers` or `lspServers` key.
- A plugin whose marketplace entry points at a different repo. This is an external plugin source. CodeHerder refuses the whole plugin.

A refused entry does not stop the other items. The other items still import.

## Warnings and collisions

**`${CLAUDE_PLUGIN_ROOT}`.** Some plugin files name a path that starts with `${CLAUDE_PLUGIN_ROOT}`. The sync imports the file and adds a warning. In a session, that path does not exist. Only native plugin loading creates it. Read these files before you pin, and fix the plugin if it needs the path.

**Slug collisions.** A slug is unique for each kind in the whole workspace tree. An admin item, an ancestor group’s item or another source’s item can already hold the slug. Then the sync refuses the new item with `slug_taken`. The other items still import.

CodeHerder never renames an item for you. Rename or delete the first item, or ask the plugin author to rename.

**The catalog budget.** All enabled catalog items share one 2 MB budget. This includes skills, agents and commands. [Skills](https://codeherder.com/docs/skills/#the-workspace-wide-size-budget) has the details.

A synced item starts enabled, so a pin can go over the budget. CodeHerder checks the owner and every workspace below it. If any of them goes over, CodeHerder refuses the **whole** pin. The old pin and the old items stay. The sync history shows the refusal.

To fix it, disable some items or choose a smaller plugin. Then try again.

## When a sync fails

| What you see | What it means |
| --- | --- |
| `plugin_source_unreadable` | The workspace has no git credential for the host, the credential does not cover the repo, or the commit does not exist. See [Integrations](https://codeherder.com/docs/integrations/) and [Connecting the GitHub App](https://codeherder.com/docs/github-app/). |
| `plugin_source_rejected` | CodeHerder cannot import the plugin at all. The marketplace file is missing, the plugin is not listed, or its source is not a path inside the repo. |
| `plugin_source_fetch_failed` | The git host or the network failed, or the archive is too large. Try again later. |

A failed sync keeps the old pin and the old items. The source page shows each attempt under **Sync history**. **Last sync** shows what the newest attempt did, item by item.

## Who can change what

Any member can read the sources and their history. Adding, previewing, pinning and removing need a workspace **owner** or **admin**. The caller must be a signed-in person. An agent’s session cannot do any of them. Preview counts as a change, because it reads the repo with the workspace’s own git credential.

A synced item is read-only. You cannot edit it or delete its definition. The error is `catalog_item_synced`, and it names the source. Change the plugin at its source and pin the new commit. You can still enable, disable and reset a synced item.

## Audit events

Each change writes an event to the [activity feed](https://codeherder.com/docs/activity/):

- `plugin_source.added`
- `plugin_source.pinned`
- `plugin_source.removed`
- `plugin_source.sync_failed`

Each item a sync creates, updates or deletes also writes `skill.created`, `skill.updated` or `skill.deleted`. These events carry the plugin source id.

## How items reach a session

A change reaches the **next** session. A session that is already running keeps what it started with.

Agents and commands reach **Claude Code only**. CodeHerder writes an agent to `.claude/agents` and a command to `.claude/commands`.

Another harness has no folder for them. Its session page shows **Unsupported harness** for each one. [Skills](https://codeherder.com/docs/skills/) reach every harness. [Harnesses](https://codeherder.com/docs/harnesses/) lists them.

The device needs a current release. An older device gets skills only. Delivered files stay out of git. If your repo already has a file at the same path, the repo file wins.

### Device opt-out

A device owner can turn synced content off for one device. Set `CH_SYNCED_CATALOG` to off. The default is on. The change applies at the next session. Items an admin wrote in the catalog still reach the device. See [Changing a device’s settings](https://codeherder.com/docs/device-settings/).

## Reading the session page

Open a session’s page and find the **Skills** section. It groups the rows. Items that an admin wrote come first. Then one group appears for each plugin source. The group title shows `plugin@marketplace` and the short commit. Each row shows its kind and one outcome:

- **Loaded** — the item is in the session.
- **Skipped** — your repo has its own file at that path.
- **Removed** — the item is no longer enabled, so CodeHerder removed its old copy.
- **Not found** and **Turned off** — a stage selection named a skill that is missing or off. See [Which skills a stage gets](https://codeherder.com/docs/stage-skills/).
- **Unsupported harness** — the harness cannot take an agent or a command.
- **Opted out** — the device owner turned off synced content.
- **Refused** — the last sync refused the item. The row names the reason.

## Commands can run shell lines

A command can contain a line that starts with `!`. Claude Code runs that line as a shell command when someone runs the command. CodeHerder does not run it. The harness’s own permission rules decide whether it runs.

This is the same class of risk as a skill script. Read each command body in the preview before you pin.

## Related guides

- [Skills](https://codeherder.com/docs/skills/) — the catalog, the kinds of item and the size budget.
- [Which skills a stage gets](https://codeherder.com/docs/stage-skills/) — a stage selection narrows skills only.
- [Changing a device’s settings](https://codeherder.com/docs/device-settings/) — `CH_SYNCED_CATALOG`.
- [Version history and going back](https://codeherder.com/docs/version-history/) — retained versions of removed items.
- [Managing workspaces](https://codeherder.com/docs/workspaces/) — how a group’s sources reach its workspaces.
