Description
deduplicatePlugins() silently drops unrelated plugins when two or more npm plugins have entry points named index.js (which is the majority of npm packages).
Root Cause
During config loading, load() in config.ts resolves plugin specifiers to file:// URLs via import.meta.resolve():
"oh-my-opencode" → file:///...node_modules/oh-my-opencode/dist/index.js
"opencode-openai-codex-auth" → file:///...node_modules/opencode-openai-codex-auth/index.ts
Then deduplicatePlugins() calls getPluginName() to extract canonical names for deduplication:
// config.ts
export function getPluginName(plugin: string): string {
if (plugin.startsWith("file://")) {
return path.parse(new URL(plugin).pathname).name // ← returns "index" for both
}
// ...
}
Since both resolve to entry points named index.js / index.ts, getPluginName returns "index" for both. deduplicatePlugins treats them as the same plugin and keeps only the last one (higher priority wins), silently dropping the other.
Steps to Reproduce
- Configure two npm plugins in
opencode.jsonc whose package entry points are named index.js/index.ts:
- Ensure both are installed in the config directory's
node_modules/
- Run
opencode debug config — only the last plugin appears in the resolved plugin array
Expected Behavior
Both plugins should be loaded. getPluginName should extract the package name from the path (e.g., oh-my-opencode from .../node_modules/oh-my-opencode/dist/index.js), not the filename.
Suggested Fix
For file:// URLs that point into node_modules/, extract the package name from the path:
export function getPluginName(plugin: string): string {
if (plugin.startsWith("file://")) {
const filePath = new URL(plugin).pathname
const nmIndex = filePath.lastIndexOf("/node_modules/")
if (nmIndex !== -1) {
const afterNm = filePath.substring(nmIndex + "/node_modules/".length)
// Handle scoped packages: @scope/pkg/dist/index.js → @scope/pkg
if (afterNm.startsWith("@")) {
const parts = afterNm.split("/")
return parts.slice(0, 2).join("/")
}
return afterNm.split("/")[0]
}
return path.parse(filePath).name
}
// ...
}
Environment
- opencode: 1.2.27
- OS: macOS (arm64)
Description
deduplicatePlugins()silently drops unrelated plugins when two or more npm plugins have entry points namedindex.js(which is the majority of npm packages).Root Cause
During config loading,
load()inconfig.tsresolves plugin specifiers tofile://URLs viaimport.meta.resolve():Then
deduplicatePlugins()callsgetPluginName()to extract canonical names for deduplication:Since both resolve to entry points named
index.js/index.ts,getPluginNamereturns"index"for both.deduplicatePluginstreats them as the same plugin and keeps only the last one (higher priority wins), silently dropping the other.Steps to Reproduce
opencode.jsoncwhose package entry points are namedindex.js/index.ts:{ "plugin": [ "oh-my-opencode", "some-other-plugin" ] }node_modules/opencode debug config— only the last plugin appears in the resolvedpluginarrayExpected Behavior
Both plugins should be loaded.
getPluginNameshould extract the package name from the path (e.g.,oh-my-opencodefrom.../node_modules/oh-my-opencode/dist/index.js), not the filename.Suggested Fix
For
file://URLs that point intonode_modules/, extract the package name from the path:Environment