[DOCS] Plugin `user_config` documentation still advertises unsafe shell-form interpolation

Open 💬 0 comments Opened Jul 11, 2026 by coygeek

Documentation Type

Incorrect/outdated documentation

Documentation Location

https://code.claude.com/docs/en/plugins-reference

Section/Topic

The plugin Monitors example, the userConfig substitution rules, and the cross-reference from hook command execution.

Current Documentation

The plugin reference currently gives this monitor example:

"command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/poll-deploy.sh ${user_config.api_endpoint}"

It also says:

“The command value supports the same variable substitutions as MCP and LSP server configs: ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}, ${CLAUDE_PROJECT_DIR}, ${user_config.*}, and any ${ENV_VAR} from the environment.”

The userConfig section says:

“Each value is available for substitution as ${user_config.KEY} in MCP and LSP server configs, hook commands, and monitor commands. … All values are exported to plugin subprocesses as CLAUDE_PLUGIN_OPTION_<KEY> environment variables.”

The Hooks page likewise says:

“Plugin hooks additionally substitute ${user_config.*} values.”

What's Wrong or Missing?

Claude Code v2.1.207 rejects ${user_config.*} in shell-form plugin hook, monitor, and MCP headersHelper commands as a shell-injection fix. The current examples and substitution rules still tell plugin authors to place these values directly in command strings, so following the published example can make a plugin fail after upgrading or encourage unsafe interpolation.

The release also gives different safe migration paths by component: hooks can use exec form with an args array or read $CLAUDE_PLUGIN_OPTION_<KEY>, while monitors and headersHelper should read the value inside the script, such as from the plugin configuration file or the MCP server's env block. The current docs do not explain this distinction.

Suggested Improvement

Replace the monitor example with a safe form that passes no user-controlled value through a shell command string and shows how the monitor script reads the corresponding plugin option. Update the substitution rules to state that ${user_config.*} is not accepted in shell-form commands for plugin hooks, monitors, or MCP headersHelper commands.

In the hooks section, show an exec-form example with args or explicitly read CLAUDE_PLUGIN_OPTION_<KEY> in the script. In the MCP headersHelper section, document the supported configuration path for plugin options, including reading the value inside the helper from the server's env block where appropriate. Add a migration note keyed to Claude Code v2.1.207 so plugin authors can update existing manifests safely.

Impact

High - Prevents users from using a feature

Additional Context

Affected Pages:

| Page | Context |
|------|---------|
| https://code.claude.com/docs/en/plugins-reference | Monitor example and general userConfig substitution rules |
| https://code.claude.com/docs/en/hooks | Plugin hook substitution and exec-form/shell-form guidance |
| https://code.claude.com/docs/en/mcp | Dynamic headersHelper configuration and plugin-provided MCP servers |

Total scope: 3 pages affected

Source: Claude Code v2.1.207

Plugin hooks/monitors/MCP headersHelper: ${user_config.*} in shell-form commands is now rejected (shell-injection fix). Hooks: use exec form (args array) or $CLAUDE_PLUGIN_OPTION_<KEY>; monitors and headersHelper: read the value inside the script (config file or the server's env block).

View original on GitHub ↗