Managed-settings enabledPlugins silently suppresses a same-named plugin supplied via --plugin-dir, so org-distributed Cowork plugins never load
Environment
- Claude Code 2.1.219 (as bundled in Claude Desktop / Cowork 1.24012.9) and 2.1.220 (native CLI)
- macOS 26.5, Apple silicon
/Library/Application Support/ClaudeCode/managed-settings.jsondeployed by MDM- Plugins distributed to Cowork users from a private GitHub marketplace through the
claude.ai org admin plugin channel (installationPreference: auto_install)
Summary
When a plugin name appears in managed-settings enabledPlugins, Claude Code resolves
that name to the marketplace-installed copy. If that marketplace copy is not installed in
the active CLAUDE_CONFIG_DIR, the plugin is dropped, and a same-named plugin supplied
on the command line via --plugin-dir is dropped with it. No warning is logged at any
level. The plugin still appears in the Cowork plugin menu, because that menu is drawn from
the org list, so it reads as installed and simply never works.
This is the exact configuration Cowork produces on a machine that has never run the CLI:
Cowork downloads each org-distributed plugin into a per-session.../local-agent-mode-sessions/<session>/<org>/rpm/plugin_<id> directory and hands each
one to Claude Code as --plugin-dir, while the MDM-deployed managed-settings file is read
on every machine whether or not the CLI is installed.
Net effect on our fleet: 7 of our plugins were dead on every advisor Mac for weeks. The
one that mattered, a plugin carrying an MCP server the whole workflow depends on, had
never loaded once on the machine it was written for.
Minimal reproduction
On any Mac carrying a managed-settings file whose enabledPlugins containssome-plugin@some-marketplace, where that marketplace is not installed in the config
dir you use below.
- Build two throwaway plugins that differ only in
name:
probe-a/.claude-plugin/plugin.json {"name":"some-plugin","version":"1.0.0",
"description":"probe","mcpServers":"./.mcp.json"}
probe-a/.mcp.json {"mcpServers":{"probe":{"command":"python3",
"args":["${CLAUDE_PLUGIN_ROOT}/scripts/stub.py"]}}}
probe-a/scripts/stub.py any minimal stdio MCP server
probe-b/... byte-identical except "name":"a-name-not-in-the-floor"
- Run each with an empty config dir:
CLAUDE_CONFIG_DIR=/tmp/cfg-a claude --plugin-dir ./probe-a mcp list
CLAUDE_CONFIG_DIR=/tmp/cfg-b claude --plugin-dir ./probe-b mcp list
Expected
Both list plugin:<name>:probe. The plugin was passed explicitly on the command line.
Actual
probe-a: No MCP servers configured. Use `claude mcp add` to add a server.
probe-b: plugin:a-name-not-in-the-floor:probe: python3 .../stub.py - ✔ Connected
probe-a is silently dropped. Nothing is written to stdout, stderr, or the Claude Desktopmain.log at any level.
Notes that narrow it
- Only managed settings trigger it. Putting the same
enabledPluginsentry in a
user-level $CLAUDE_CONFIG_DIR/settings.json does not reproduce; the plugin loads
normally. So the code path treats a managed enable as authoritative in a way a user
enable is not.
- It is the whole plugin, not just MCP. We confirmed against our own OTel telemetry
(plugin_loaded events from Cowork sessions): on one advisor machine, 11 plugins loaded
and every one of the 7 named in managed enabledPlugins was absent, while all 10
org-distributed plugins not named there loaded. mcp list is just the cheapest way to
see it.
- Cowork's own logs are actively misleading here.
[PluginScan]and
[RemotePluginManager] both emit per-command warnings for the suppressed plugin
(Skipping legacy command "some-plugin:some-command" — name collides with skills/ entry),
which reads as "the plugin loaded and one command was skipped", and
[RemotePluginManager] Sync complete: 0 downloaded, 0 removed, 0 orphans cleaned reports
a healthy sync. The plugin directory is present on disk and complete.
Impact
Any organization that both (a) pins plugins through MDM-deployed managed-settings for its
CLI users and (b) distributes the same plugins to Cowork users through the claude.ai org
channel will have those plugins silently non-functional for every Cowork-only user. The
two mechanisms are documented independently and nothing warns that combining them is
destructive.
What would have saved us the week
Any one of these:
- Load the
--plugin-dircopy when the managed-enabled name cannot be resolved to an
installed marketplace plugin. It is the only copy present; preferring nothing over it
is never the better outcome.
- Log one warning at
warnlevel naming the plugin and the reason. - Have the Cowork plugin menu reflect what actually loaded rather than what the org
distributes, so "it is in my menu" stops meaning "it is installed and working".
Our workaround
Remove every Cowork-distributed plugin name from managed enabledPlugins, and rely on
per-user generated settings for CLI machines. Documented in an internal ops doc.