[BUG] Plugin sensitive userConfig not persisted to keychain/credentials.json/settings.json — values lost on Claude Code restart

Resolved 💬 2 comments Opened May 26, 2026 by atoj-taiga-manabe Closed Jun 27, 2026

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?

When a plugin declares userConfig with sensitive: true and the user enters the value via /plugin → Installed → <plugin> → Configure options → Save, the value is silently not persisted anywhere:

  • ❌ Not in macOS Keychain (no new entry created)
  • ❌ Not in ~/.claude/.credentials.json (file does not exist)
  • ❌ Not in ~/.claude/settings.json under pluginConfigs[<plugin>].options
  • ❌ Not in ~/.claude.json top-level pluginSecrets (key not even initialized)

The value works for the current session only (in-memory env substitution into the MCP server spawn succeeds), then is lost on Claude Code restart. The user must re-enter the API key on every cold start.

No error is shown to the user. Debug logs (--debug --debug-file ...) show no save-related log lines (saveMcpServerUserConfig, Saved user config, saveConfigWithLock, secureStorage, [keychain] — all 0 occurrences).

What Should Happen?

Per Plugins reference docs:

Sensitive values go to the system keychain (or ~/.claude/.credentials.json where the keychain is unavailable).

The value entered in Configure options should be persisted to macOS Keychain and survive Claude Code restarts.

Reproduction Steps

  1. Create a minimal plugin with this plugin.json:

``json
{
"name": "test-sensitive",
"version": "0.1.0",
"description": "test",
"author": { "name": "x" },
"userConfig": {
"api_key": {
"type": "string",
"title": "API key",
"description": "test sensitive",
"sensitive": true
}
},
"mcpServers": {
"echo": {
"command": "sh",
"args": ["-c", "echo \"got=${API_KEY}\" >&2; sleep 99999"],
"env": { "API_KEY": "${user_config.api_key}" }
}
}
}
``

  1. Install plugin in Claude Code
  2. /plugin → Installed → test-sensitive → Configure options → enter hello-world → Save
  3. /mcp → confirm echo connected (works — value used for spawn)
  4. Quit Claude Code fully (/exit)
  5. Inspect storage:

``bash
security find-generic-password -s "test-sensitive" 2>&1 # not found
ls ~/.claude/.credentials.json # ENOENT
python3 -c 'import json; d=json.load(open("~/.claude.json".replace("~", "$HOME"))); print(d.get("pluginSecrets",{}))' # {}
python3 -c 'import json; d=json.load(open("~/.claude/settings.json".replace("~", "$HOME"))); print(d.get("pluginConfigs",{}))' # {}
``

  1. Restart Claude Code → MCP server fails because ${user_config.api_key} substitutes to empty

Workaround

None for sensitive: true. For sensitive: false values, #39827 suggests manually editing settings.json under pluginConfigs, but that defeats the secure-storage promise.

I worked around in my plugin by replacing userConfig with a wrapper shell script that reads from macOS Keychain directly via security find-generic-password, requiring users to register the key manually with security add-generic-password before installing.

Environment

  • Claude Code: 2.1.150 (native installer, ~/.local/share/claude/versions/2.1.150)
  • macOS: 25.4.0 (Darwin) on Apple Silicon
  • Plugin source: GitHub-hosted private marketplace, installed via /plugin marketplace add + /plugin install

Related Issues

  • #39455 Plugin userConfig values not prompted on enable (open, macOS) — workaround = Configure options manually, but that workaround itself is broken for sensitive: true (this report)
  • #39827 userConfig prompt not shown during plugin install (closed not-planned, Windows)
  • #40600 Cowork: Personal marketplace plugin installation lost after app restart (closed not-planned)

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗