Summary
OpenCode resolves plugin entries with import.meta.resolve() before calling deduplicatePlugins(). When a package resolves to a file URL ending in dist/index.js, getPluginName() reduces it to basename index, so multiple unrelated plugins collide and only the last one survives.
Repro
Given config like:
{
"plugin": [
"opencode-claude-auth",
"oh-my-opencode",
"oc-codex-multi-account@latest",
"opencode-memory-plugin"
]
}
and locally installed packages that resolve to:
file:///.../node_modules/opencode-claude-auth/dist/index.js
file:///.../node_modules/oc-codex-multi-account/dist/index.js
file:///.../memory-plugin/index.ts
Config.loadFile() resolves those package names first, then deduplicatePlugins() sees all three file URLs as the canonical plugin name index.
Result: only the last index plugin is kept, and the others are silently dropped.
Relevant code
packages/opencode/src/config/config.ts:1316 resolves plugin specifiers via import.meta.resolve()
packages/opencode/src/config/config.ts:481 canonicalizes file URLs using path.parse(...).name
packages/opencode/src/config/config.ts:503 deduplicates based on that canonical name
Why this is device-dependent
It only breaks on machines where the package is already locally resolvable during config parsing (for example via ~/.config/opencode/node_modules). On machines where the bare package name stays unresolved until plugin load time, dedupe uses the package name and everything works.
Expected
Unrelated plugins should not collide just because their resolved entrypoints are named index.js.
Actual
Different plugins silently collapse to the same canonical name index.
Workaround
Point the plugin entry at a uniquely named file URL instead of the bare package name, e.g.:
{
"plugin": [
"file:///.../node_modules/opencode-claude-auth/claude-auth-plugin.js"
]
}
Proposed fixes
Either of these would solve it in core:
- Deduplicate before
import.meta.resolve() so package names remain package names.
- For resolved file URLs, derive the canonical plugin name from the nearest
package.json name instead of the basename.
- At minimum, preserve the original plugin specifier for dedupe instead of using only the resolved file basename.
Summary
OpenCode resolves
pluginentries withimport.meta.resolve()before callingdeduplicatePlugins(). When a package resolves to a file URL ending indist/index.js,getPluginName()reduces it to basenameindex, so multiple unrelated plugins collide and only the last one survives.Repro
Given config like:
{ "plugin": [ "opencode-claude-auth", "oh-my-opencode", "oc-codex-multi-account@latest", "opencode-memory-plugin" ] }and locally installed packages that resolve to:
file:///.../node_modules/opencode-claude-auth/dist/index.jsfile:///.../node_modules/oc-codex-multi-account/dist/index.jsfile:///.../memory-plugin/index.tsConfig.loadFile()resolves those package names first, thendeduplicatePlugins()sees all three file URLs as the canonical plugin nameindex.Result: only the last
indexplugin is kept, and the others are silently dropped.Relevant code
packages/opencode/src/config/config.ts:1316resolves plugin specifiers viaimport.meta.resolve()packages/opencode/src/config/config.ts:481canonicalizes file URLs usingpath.parse(...).namepackages/opencode/src/config/config.ts:503deduplicates based on that canonical nameWhy this is device-dependent
It only breaks on machines where the package is already locally resolvable during config parsing (for example via
~/.config/opencode/node_modules). On machines where the bare package name stays unresolved until plugin load time, dedupe uses the package name and everything works.Expected
Unrelated plugins should not collide just because their resolved entrypoints are named
index.js.Actual
Different plugins silently collapse to the same canonical name
index.Workaround
Point the plugin entry at a uniquely named file URL instead of the bare package name, e.g.:
{ "plugin": [ "file:///.../node_modules/opencode-claude-auth/claude-auth-plugin.js" ] }Proposed fixes
Either of these would solve it in core:
import.meta.resolve()so package names remain package names.package.jsonname instead of the basename.