You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin config hooks can mutate process-shared config state via shallow-merged nested objects #41916
Workspace-level config state shares nested objects (e.g. mcp) with the process-global config cache when the workspace has no project-level value for that key (mergeDeep shallow reference). Plugin config hooks receive that mutable object and can write into it, leaking state into every other workspace on the same server.
Details
Config.loadInstanceState merges the global config into the per-workspace result via mergeConfig (remeda mergeDeep). For nested keys absent on the workspace side, the merged value is the same object reference as the global cache's object (Config.getGlobal / cachedGlobal).
Plugin config hooks are invoked with cfg = config.get() — the workspace's live config-state object — at packages/opencode/src/plugin/index.ts (hook.config?.(cfg)). Plugins commonly mutate it in place (config.mcp[name] ??= {...}).
When such a plugin runs on a workspace that has no project-level mcp config, the write lands on the shared global mcp object. Every other workspace attached to the same server (opencode serve with multiple attached TUIs) then sees those MCP servers in its config//mcp status — disabled when the other workspace's MCP state predates the write, connected when it was created after.
Repro
opencode serve; attach workspace A (any directory, no project-level mcp config) running a plugin whose config hook does config.mcp[name] ??= {...}.
Attach workspace B (any directory, no project-level mcp config).
GET /mcp?directory=<B> shows A's plugin-registered servers.
Fresh opencode --local processes are unaffected (fresh global cache); existing cached per-workspace states are unaffected until re-created. The leak persists for the life of the server process.
Impact
Cross-workspace config isolation is broken on shared servers. A workspace that only adds a project-level mcp config happens to be immune (deep merge then produces a fresh object), which makes the behavior depend on unrelated config details.
Suggested directions
Deep-copy (or otherwise de-alias) nested config objects during merge so workspace configs never reference the global cache.
Hand plugin config hooks an immutable copy (or a draft) of the config instead of the live state object, with hook return values (or explicit mutation APIs) as the update path.
Document that config hooks must not mutate their input.
Verified against v1.18.13; the relevant code paths appear unchanged on current dev at the time of writing.
Activity
github-actions commentedon Oct 11, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.