[BUG] A `userConfig` field with `options` prevents plugin installation
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
A plugin whose plugin.json declares a userConfig entry of "type": "string" carrying anoptions array cannot be installed. The install fails immediately and reports only a
generic message — no status, no field name, nothing written to any log. Retrying never
succeeds.
options is documented as valid for string-typed userConfig fields, so this appears to be
a validation or parsing defect rather than a manifest error.
The silence is the worse half of the bug. With no field named and nothing logged, the only
way to find the cause is to bisect the manifest by hand — which took seven throwaway
plugins to narrow down.
What Should Happen?
Either:
- The plugin installs, and the field renders as a picker over the supplied values; or
- If
optionsis genuinely unsupported, installation fails with a message naming the
rejected field — as already happens for an unrecognised key (see #62400, where the
runtime reports userConfig.framework: Unrecognized key: "enum").
Silently refusing a documented key with no diagnostic is the part that should not happen
either way.
Error Messages/Logs
The entire user-visible output:
Plugin couldn't be installed. Try again.
No status code and no field name. Nothing relevant was written to either log location:
- `%LOCALAPPDATA%\Claude\logs\main.log` — not touched during the failed installs; newest
plugin-related entries were weeks old
- `%LOCALAPPDATA%\claude-cli-nodejs\Cache\<project>\` — contains MCP server logs only, no
plugin installation log
The marketplace clone on disk was complete and correct after the failure, so the fetch
succeeded and only installation failed.
Steps to Reproduce
- Create a marketplace repository containing a single plugin.
- Give the plugin this
plugin.jsonand nothing else:
{
"name": "probe-cfg-options",
"version": "1.0.0",
"description": "Isolates a string userConfig field that carries an options list.",
"userConfig": {
"example_choice": {
"type": "string",
"title": "Example choice",
"description": "Picks one of a fixed set of values.",
"default": "alpha",
"options": ["alpha", "beta", "gamma", "delta", "epsilon zeta"]
}
}
}
/plugin marketplace add <repository url>
/plugin install probe-cfg-options@<marketplace>
Result: Plugin couldn't be installed. Try again.
Control: delete the "options" line, leaving everything else identical. The plugin
then installs.
Claude Model
_No response_
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.273
Platform
Other
Operating System
Windows
Terminal/Shell
Other
Additional Information
How it was isolated
Each row is the previous working plugin plus exactly one change.
| Plugin | Difference from the row above | Installs |
|---|---|---|
| bare probe | manifest + one skill | yes |
| + MCP server | an .mcp.json MCP server declaration | yes |
| + binaries | 4.5 MB of published .NET files, incl. a nested runtimes/ folder | yes |
| + userConfig | a three-field userConfig block | no |
| userConfig: string | one required string field | yes |
| userConfig: boolean | one boolean field, default false | yes |
| userConfig: options | one string field carrying options | no |
Not the MCP declaration, not the binaries, not userConfig as such, not string fields, not
boolean fields. Only options.
Ruled out
- MAX_PATH — deepest path inside the plugin was 136 characters against the 260 limit,
even with LongPathsEnabled = 0.
- Duplicate MCP declaration —
plugin.jsonreferenced./.mcp.jsonwhile that file is
already scanned by default. A real redundancy, fixed, but not the cause.
- Marketplace or source resolution — a bare probe installs from the same marketplace.
- Stale marketplace cache — the cached clone did lag several commits behind and cost a
round of testing, but updating it did not fix the install. Separately worth noting:
/plugin install gives no indication that the marketplace copy is out of date.
Not a duplicate
Searched open and closed issues. Several userConfig bugs are on file, but all concern
values not being collected or substituted after a successful install. Here
installation itself never completes.
| Issue | Reports | Why this differs |
|---|---|---|
| #62400 | userConfig with an enum array rejected at runtime: Unrecognized key: "enum", while claude plugin validate passes | enum is undocumented; options is documented. That failure names the field — this one says nothing. Closed as not planned. |
| #39455 | userConfig values not prompted on enable | The plugin installs |
| #39827 | Substitution runs before values are collected | Installation succeeds first |
| #89749 | userConfig never collected on Desktop / VS Code | Installation succeeds |
| #80904 | Esc during post-install config leaves plugin registered but unloaded | User-cancelled, not rejected |
The comparison with #62400 is the useful one: the runtime can name a bad userConfig
key, so options is likely failing earlier, or on a different path from that check.
Not verified: whether claude plugin validate passes the repro manifest. If it does, this
is the same validator/runtime divergence as #62400, but on a key documented as valid.
Environment
- Windows 11 Pro 26200
- Claude Code desktop app
- Marketplace served from a self-hosted Git repository over HTTPS with Windows integrated
authentication (not GitHub, GitLab or Bitbucket)
Workaround
Drop options and name the valid values in description instead. The field becomes free
text with a default — the picker is lost, nothing else is.
Suggested fixes
- Report which manifest field was rejected, instead of "Plugin couldn't be installed."
- Write installation failures to a log the user can reach.
- Accept
optionson string fields, or document that it is unsupported.