Claude Code’s v2.1.1431, released May 16, 2026, adds enforcement to the plugin dependency graph: claude plugin disable now refuses to disable a plugin if another enabled plugin declares it as a dependency, and claude plugin enable now cascades to enable that plugin’s dependencies along with it. Before this update, enable and disable touched only the named plugin and surfaced a dependency-unsatisfied error on the next load. The auto-installation of declared dependencies is a separate behavior that runs at claude plugin install time, not on enable; enabling a plugin whose dependency is not yet installed fails with the exact claude plugin install command needed to fix it [Updated June 2026].
What the Enforcement Actually Does
When you run claude plugin disable on a plugin that another enabled plugin depends on, the command exits with an error and prints a pre-formatted disable chain: the exact sequence of claude plugin disable calls needed to unwind the dependency graph in order, ending with the one you asked for. The error names the dependents explicitly. For a deploy-kit that depends on secrets-vault, disabling secrets-vault alone fails with a message like secrets-vault is still required by deploy-kit followed by claude plugin disable deploy-kit@acme-tools && claude plugin disable secrets-vault@acme-tools2. The hint is copy-pasteable, which is either convenient or an acknowledgment that multi-step dependency unwinding is now a real user workflow.
claude plugin enable enables a plugin’s dependencies at the same scope, recursing through transitive dependencies, and the success message lists everything it enabled beyond the plugin you named [Updated June 2026]. It does not silently install anything. If a dependency is not installed, enable fails and prints the claude plugin install command for each missing one; if a dependency is blocked by an organization plugin policy, enable fails and names it; if a dependency is set to false at a higher-precedence scope, enable fails and tells you to enable it there or pass --scope2. Enable writes an explicit true for the named plugin and each dependency, which overrides a dependency’s own defaultEnabled: false rather than respecting it.
The Dependency Schema
Plugin authors declare dependencies in the dependencies array of their manifest3, for example:
"dependencies": ["helper-lib", { "name": "secrets-vault", "version": "~2.1.0" }]Bare strings track whatever version the dependency’s marketplace currently provides; the object form pins a semver range such as ~2.1.0, ^2.0, >=1.4, or =2.1.0, and the dependency resolves to the highest tagged version that satisfies it. The plugins reference4 confirms both forms are valid. The version field accepts any expression Node’s semver package supports, and pre-release versions like 2.0.0-beta.1 are excluded unless the range opts in with a pre-release suffix such as ^2.0.0-02. Version constraints predate the enable/disable enforcement; they require Claude Code v2.1.110 or later [Updated June 2026].
What the schema still does not provide is a committed lockfile that pins a whole dependency graph and travels with a project, the way package-lock.json does. Conflict resolution itself is specified, contrary to a common assumption when the feature first shipped, and is covered below.
The Three-Week Arc
The disable/enable enforcement in v2.1.143 is not the first piece of this infrastructure. The version-constraint and resolution layer landed earlier, in v2.1.110, which is where semver ranges, cross-marketplace controls, and range intersection arrived [Updated June 2026]. claude plugin prune, which removes orphaned auto-installed dependencies (also accessible as claude plugin autoremove), shipped in v2.1.121 on April 285. The enable/disable trio, cascade-enable on enable, block on unsafe disable, prune for orphan cleanup, was assembled on top of that in stages over roughly three weeks. plugin uninstall --prune extends this by scanning for and removing the now-orphaned auto-installed dependencies after the named plugin is gone; plugins you installed directly are never pruned2. The dependency machinery has been stable since: Claude Code reached v2.1.193 by late June 20261 with no further changes to the resolution, conflict, or enforcement behavior described here [Updated June 2026].
apt, Not the VS Code Extension Store
The VS Code extension model treats every extension as independent. No extension declares formal runtime dependencies on another in a way the host enforces; they coexist or conflict at runtime, and users manage failures manually. Claude Code’s enforcement sits with apt rather than npm: the host resolves a declared dependency graph, installs transitive deps at install time, holds a single shared version per dependency, and refuses operations that would leave the graph broken. This is the layer that turned the official plugin ecosystem from a loose directory of independently-installed plugins into something with an actual dependency tree underneath it.
That shift has a cost. The VS Code approach is casual to author against: write an extension, publish it, users install it. The npm approach requires correctly declaring your dependencies, versioning them with care, and accepting that a bad semver range will surface as a user-visible error rather than a quiet runtime surprise. Plugin authors who have been vague about dependency declarations will find that sloppiness exposed now.
The Author Burden Shifts
Prior to this update, a plugin that implicitly relied on helper-lib being installed worked fine as long as the user happened to have it. A fresh profile missing helper-lib would produce a runtime error with no guidance. A declared dependency flips this: if helper-lib is in your manifest, claude plugin install pulls it in automatically and lists it in the install output, and claude plugin enable will not let you turn your plugin on while helper-lib is missing or disabled. If another plugin depends on yours, you cannot be disabled without an explicit chain [Updated June 2026].
The failure mode moves from silent runtime breakage to an explicit error at install, enable, or disable time. That is strictly better for users, and strictly more demanding on authors, who now need a correct manifest or their users will encounter errors they cannot easily diagnose.
The Resolver Anthropic Already Chose
The interesting question when this feature shipped was whether Claude Code would resolve version conflicts npm-style (nested installs per dependent, each with its own version tree) or apt-style (a single shared version, with incompatibility surfaced as an error). The answer, documented under “How constraints interact,” is apt-style [Updated June 2026]. There is one install of a shared dependency, resolved to the highest tag that satisfies every installed plugin’s range simultaneously2.
The documented cases are concrete. If plugin A requires ^2.0 and plugin B requires >=2.1, both load against one install at the highest 2.x tag at or above 2.1.0. If A requires ~2.1 and B requires ~3.0, installing B fails with range-conflict, and A plus the dependency stay exactly as they were. A =2.1.0 pin holds the dependency at 2.1.0 and makes auto-update skip newer releases for as long as the pinning plugin is installed. Auto-update itself respects the intersection: it fetches the highest tag satisfying every range rather than the marketplace’s latest, so a constrained dependency keeps receiving in-range patches. When no tag satisfies all ranges, the update is skipped and the skip is reported in /doctor and the /plugin Errors tab, naming the constraining plugin2.
That choice has consequences. The apt model is clean for the common case and predictable to reason about, but it means one plugin’s tight pin can freeze a dependency for every other plugin that shares it, and an incompatible pair simply cannot coexist. There is no escape hatch into nested installs. The casual plugin author who writes =2.1.0 because that is the version they tested against is, without necessarily realizing it, vetoing every newer release of secrets-vault for anyone who also runs deploy-kit. npm absorbed this pain with node_modules bloat; Claude Code absorbed it by making the conflict a hard, named error instead. The remaining genuine gap is reproducibility across machines, which a committed lockfile would close and which the current tag-resolution scheme only approximates.
How Versions Actually Resolve: Git Tags, Not a Registry
The detail that separates this from npm is where versions come from. There is no package registry with a version index. Constraints resolve against git tags on the marketplace repository, named {plugin-name}--v{version}, where the version matches the version field in that commit’s plugin.json2. Authors create them with claude plugin tag --push, which derives the tag name from the manifest and the enclosing marketplace entry, validates the plugin contents, checks that plugin.json and the marketplace entry agree on the version, requires a clean working tree under the plugin directory, and refuses if the tag already exists. A plain git tag secrets-vault--v2.1.0 is equivalent if you keep the two version fields in sync by hand.
When Claude Code installs a plugin that declares { "name": "secrets-vault", "version": "~2.1.0" }, it lists the marketplace’s tags, filters to those prefixed secrets-vault--v, and fetches the highest that satisfies the range. If no tag matches, the dependent plugin is disabled with a no-matching-tag error that lists the available versions. The resolved tag’s semver is recorded separately from whatever plugin.json says at that commit, so a stale manifest version cannot poison the constraint check, and the cache directory carries a 12-character commit-SHA suffix so that a maintainer force-moving a tag yields a fresh cache rather than stale content. One caveat that bites teams using npm-backed marketplace sources: tag resolution is git-only, so for npm sources the constraint does not control which version is fetched. It is still checked at load, and a version outside the range disables the plugin with dependency-version-unsatisfied2.
Dependency problems are not buried in a log. They surface in claude plugin list (with a machine-readable errors field under --json), in the /plugin interface, and in /doctor, and the affected plugin stays disabled until the error clears. The documented set is small and specific: dependency-unsatisfied (declared but not installed or disabled), range-conflict (ranges cannot be combined, or a range is invalid semver), dependency-version-unsatisfied (installed version outside the range), and no-matching-tag2. This versioning discipline arrived alongside the broader official plugin ecosystem, and it matters most to the teams shipping internal marketplaces, where the way developers actually configure Claude Code determines how many of these errors they hit in practice.
Cross-Marketplace Dependencies Are Blocked by Default
The supply-chain implication is handled more carefully than the rest of the spec. By default, Claude Code refuses to auto-install a dependency that lives in a different marketplace than the plugin declaring it, which stops one marketplace from silently pulling plugins out of a source the user never reviewed2. Allowing it is explicit: the maintainer of the root marketplace, the one hosting the plugin the user is installing, adds the target marketplace name to allowCrossMarketplaceDependenciesOn in marketplace.json. Only the root marketplace’s allowlist is consulted, so trust does not chain through intermediaries. A missing or insufficient allowlist fails the install with a cross-marketplace error naming the exact field to set, and a user who trusts the dependency can still install it manually first to satisfy the constraint without widening anyone’s allowlist.
Frequently Asked Questions
Does the plugin manifest support optional or dev-only dependencies?
No. The manifest exposes a single dependencies field with no equivalent of npm’s devDependencies or optionalDependencies. Every declared dependency is treated as required and enforced uniformly, so a plugin that can degrade gracefully when a dependency is absent has no way to express that in the manifest.
What should plugin authors do right now to avoid breaking user installs?
Audit runtime imports against the manifest and backfill any missing dependencies entries, then tag a release with claude plugin tag --push so the constraint can resolve. A plugin that calls into another plugin’s exports without declaring that relationship works fine on existing installs where the transitive dependency is already present, but produces an unguarded runtime error for new users who run claude plugin install on a fresh profile, since only declared dependencies get pulled in automatically [Updated June 2026].
How does claude plugin prune differ from npm prune?
npm prune strips everything not listed in package.json from node_modules. claude plugin prune only removes auto-installed transitive dependencies whose parent was already removed, it will not touch manually installed plugins regardless of whether anything references them. The scope is strictly orphan cleanup, not full tree reconciliation.
What specifically happens when two plugins require conflicting versions of a dependency?
It is specified, and the answer is apt-style [Updated June 2026]. Claude Code intersects the declared ranges and resolves to the highest version satisfying all of them. When the ranges cannot be combined, the install that would introduce the conflict fails with a range-conflict error, and the previously installed plugin and dependency are left untouched. There is no nested-install fallback the way npm provides one, so an incompatible pair cannot coexist; one of them has to widen its range or be removed2.
What historical precedent suggests Claude Code will need a lockfile mechanism?
npm introduced package-lock.json in npm v5 (2017) because transitive dependency versions resolved differently across installs, breaking reproducibility. The same pressure applies here. Resolution is deterministic for a fixed set of git tags, since it always takes the highest tag satisfying every range, but the tag set is not fixed: if an upstream marketplace publishes a new in-range release between two installs, the second machine resolves to a higher tag than the first. Without a committed lockfile that records and ships the exact resolved graph, two installs of the same plugin set are not guaranteed identical, and the =2.1.0 exact-pin is currently the only way an author can force one [Updated June 2026].