# Claude Code Plugin Upgrades: Version Pinning and Rollback

> A plugin upgrade does not break the build. It changes how the agent behaves, silently, mid-sprint. How to pin a version, notice a move, and roll back.

URL: https://www.matthewswong.com/en/blog/claude-code-plugin-rollback-version-pinning/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2026-09-10
- Updated: 2026-09-10
- Category: DevOps

> **Key takeaway:** A Claude Code plugin upgrade rarely breaks a build; it changes how the agent behaves. Pin the exact version, gate every upgrade on an eval run and a claude plugin details diff, and keep a changelog of behavioural effects, because there is no downgrade command to fall back on.

The week a plugin upgrade lands is the week nobody blames the plugin. Auto-update fires in the background a few minutes after each session starts, the version on disk moves, nothing errors, and the only signal anyone sees is a prompt to run reload-plugins. Three days later the complaint is that the agent feels worse this week, not that anything is broken.

This post is about the failure mode that belongs to shipping instructions as a dependency, and how to make it survivable: why pinning an exact version is the default rather than paranoia, how to tell that a marketplace moved a tag under you, how to turn behaviour changed into a number, and how to roll a plugin back when the CLI has no downgrade command. Every command here was checked against Claude Code 2.1.259 and the plugin docs.

## An upgrade with no error message

A plugin is a versioned bundle of skills, subagents, hooks, MCP server definitions and instructions, which means its payload is behaviour rather than code your build compiles. When a library upgrade breaks, something throws and the stack trace names the library. When a plugin upgrade breaks, the agent keeps working and quietly works differently, so the report arrives as a mood rather than a bug.

| What the team says | What actually changed | Where it was visible |
| --- | --- | --- |
| It stopped using our migration checklist | A skill description was tightened and no longer matches how people phrase the request | Nowhere. The skill simply never fires |
| Every edit feels slow today | A new PostToolUse hook runs a formatter over the file on every write | Wall-clock time per turn, and the debug log |
| It keeps picking the wrong test runner | One sentence in an instruction file was reworded, so a different default won | The transcript, but only if you already suspect the plugin |
| A tool call failed by name | An MCP server in the bundle renamed one of its tools | The transcript, immediately. This is the easy one |

Two mechanics make the attribution hard. Claude Code checks for marketplace and plugin updates after your session starts, with a random delay of up to ten minutes, so the version you launched on is not necessarily the version you finish on, and the notification you get is an invitation to reload rather than a summary of what changed. And a plugin that updates mid-session leaves hooks, monitors, MCP servers and LSP servers pointing at the previous version's path until you reload, so for part of that afternoon half the plugin is old and half is new.

## The default is not a version

Claude Code uses the plugin's version as the cache key that decides whether an update exists, and for every source type except command it resolves that version from the first of five sources that is set: the version field in plugin.json, then the version in the marketplace entry, then the git commit SHA of the source for github, url, git-subdir and relative-path sources, then the SHA-256 digest of an archive source shortened to twelve characters, and finally unknown for an npm source or a local directory outside a git repository.

The third rung is the one that surprises people. If the publisher declares no version field anywhere, the commit SHA becomes the version, so every commit they push to the ref you track is a release to you. The docs recommend exactly that for internal plugins under active development, which is reasonable for the author and poor for a team of twelve, because the author is then choosing your update cadence. Read the marketplace entry before you install: a plugin with no declared version has no release you can name in an incident review.

## Auto-update is a decision you have probably not made

The defaults are not symmetric, and that is worth knowing before you assume anything is frozen. The official marketplace and most other official Anthropic marketplaces have auto-update enabled by default; third-party and local development marketplaces have it disabled by default. So the catalogue you vetted least is the one that stays still, and the plugins you trust most are the ones that move without asking. Neither default is wrong, but exactly one of them matches what your team believes is happening.

Turning auto-update off is not the same as pinning. It stops the background refresh, but an install or update you run by marketplace name refreshes that marketplace first anyway, even with auto-update off and even with DISABLE_AUTOUPDATER set. The only immovable pin is a sha on a plugin source inside a catalogue you control. Marketplace sources accept a ref and no sha; plugin sources accept both, and when both are set the sha wins and Claude Code checks out that commit directly.

> Pinning and forgetting is its own outage. A plugin frozen at a commit never receives the upstream fix for the hook that hangs on large repositories, and you rediscover that months later with no memory of why the pin exists. Put the reason and a review date in the same commit as the pin, or you have traded a surprise upgrade for a surprise non-upgrade.

## Tracking a ref is not tracking a version

You can add a marketplace at a branch or tag by appending a ref, and it reads like a pin. It is not one. A marketplace added with a branch or tag ref updates to the latest commit of that ref, not the repository's default branch, so a publisher who deletes and re-creates v2.3.0 on a later commit, which is a normal thing for a small team to do, moves you on the next refresh without a word. Tracking a branch is the same arrangement with the pretence removed: you have a name, not a version.

The detection is two commands and it belongs in your repository rather than in somebody's memory. The marketplace listing gives you the local clone path, and git in that path gives you the commit actually checked out. Commit both snapshots and let CI diff them on every run, because a moved tag shows up as a changed SHA beside an unchanged version string, and that pairing is the one signature you cannot see any other way.

*Read the tags downward. The bands that error are the ones you fix within the hour; the band at the bottom is the one a team feels for a week without ever naming it.*

> Snapshot claude plugin list --json into the repository at every release. It carries version, scope, installPath, installedAt and lastUpdated for each plugin, so when did this change stops being a conversation and becomes a git log.

## Turn it feels worse into a number

Do the cheap check first, because it takes seconds and makes no model calls. claude plugin details prints the plugin's component inventory grouped as Skills, Agents, Hooks, MCP servers and LSP servers, along with an always-on figure for the tokens its listing text adds to every session. Snapshot that before the update and after it, then diff the two. A component that appeared and an always-on figure that grew are both changes in the agent's defaults, not in its options.

Then the measured check. claude plugin eval accepts an installed plugin by the id that joins its own name to its marketplace name, not only a path, and when a plugin resolves it adds a no-plugin baseline arm by default, so the run reports a delta rather than a bare score. The threshold flag exits 1 when any case falls below it, which is the whole difference between a gate and a report. The honest limit is that an eval suite measures only what you wrote cases for, so the case worth writing first is the prompt your team actually types.

## There is no downgrade command

The plugin CLI covers details, disable, enable, eval, init, install, list, marketplace, prune, tag, uninstall, update and validate. There is no downgrade, and install takes no version argument, so install the old one is not a thing you can type. A rollback is therefore composed from four moves, and only the third of them is durable.

Knowing it worked takes more than a version string. The version in claude plugin list tells you what is installed on disk; reload-plugins is what moves hooks, MCP servers and LSP servers off the other version's path; monitors only follow on a full session restart; and the eval case that regressed is the thing that tells you the behaviour came back rather than just the bytes. Check them in that order, because each one can pass while the next still fails.

*The middle panel is the one people skip, and it is where a rollback usually goes wrong: a changed version string is not proof until hooks and monitors have moved too.*

## Keep a changelog of behavioural effects

Semantic versioning records the publisher's intent. Your changelog records your team's experience, and those are different documents: a patch release that reworded one instruction is a trivial bump upstream and a working day of confusion here. One entry per upgrade you accepted, five fields, kept in the repository next to the pin.

1. Both version strings and both commit SHAs, because the new one alone tells the next person nothing.
2. The prompt whose behaviour changed, quoted as somebody actually typed it rather than paraphrased.
3. The always-on token delta from the details diff, and any component that appeared or vanished.
4. The eval case that moved, with its score before and after, or a note that no case covered it, which is itself the finding.
5. What ended it: a pin, a fix upstream, or a decision to live with it, dated, with the review date if you pinned.

The payoff is the second incident. The first one costs three days of not knowing where to look. With this file the second one costs an afternoon, because somebody greps the skill name and finds an entry saying that this description was tightened in 2.4.0 and stopped firing on the phrasing this team uses.

Treat a plugin as an instruction dependency rather than a code dependency and the practices follow: pin an exact commit in a catalogue you own, gate every upgrade on a details diff and an eval run, snapshot what is installed into the repository so a moved tag becomes visible, and write down the behavioural effect instead of the version number. The upgrade that changes nothing you can see is the one that earns the paperwork.

## Sources and further reading

- Claude Code docs, Plugins reference: version management, the plugin cache and the plugin CLI
- Claude Code docs, Discover and install plugins: auto-update defaults per marketplace
- Claude Code docs, Create and distribute a plugin marketplace: ref and sha pinning on a plugin source
- Claude Code docs, Settings reference: extraKnownMarketplaces and its autoUpdate field
- Claude Code docs, Create plugins: testing with plugin-dir and how it overrides an installed plugin

## Source links

- https://code.claude.com/docs/en/plugins-reference
- https://code.claude.com/docs/en/discover-plugins
- https://code.claude.com/docs/en/plugin-marketplaces
- https://code.claude.com/docs/en/settings-reference
- https://code.claude.com/docs/en/plugins

## FAQ

### Can I install a specific version of a Claude Code plugin?

Not directly. The claude plugin install command takes no version argument, so the version you receive is whatever the marketplace catalogue resolves at that moment. To control the version you have to control the catalogue: add a sha to the plugin's source in a marketplace.json you own, which pins it to an exact commit.

### How do I roll back a Claude Code plugin upgrade?

There is no downgrade command, so a rollback is composed. Disable the plugin to stop the behaviour, relaunch with --plugin-dir pointed at the previous version's directory in the plugin cache to get through the day, then move the sha in your own catalogue back to the last commit you evaluated and run claude plugin update. Confirm with claude plugin list and a reload.

### Do Claude Code plugins update automatically?

It depends on the marketplace. The official Anthropic marketplaces have auto-update enabled by default, while third-party and local development marketplaces have it disabled by default. When it is on, Claude Code checks for updates after your session starts with a random delay of up to ten minutes, and prompts you to reload if anything changed.

### What happens if a plugin declares no version at all?

For a git-based source, the resolved commit SHA becomes the version. That means every commit the publisher pushes to the ref you track counts as a new release for you, and an update arrives whenever that commit changes. It is a reasonable default for a plugin under active internal development and a poor one for anything a team depends on.

### How can I tell whether a plugin upgrade changed the agent's behaviour?

Use two checks. Diff claude plugin details before and after the update to see components that appeared or vanished and how the always-on token cost moved. Then run claude plugin eval against the installed plugin id with a threshold, which adds a no-plugin baseline arm and exits 1 when any case scores below the bar you set.

---

- HTML version: https://www.matthewswong.com/en/blog/claude-code-plugin-rollback-version-pinning/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/claude-code-plugin-rollback-version-pinning/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
