Description
deduplicatePlugins in the config module uses path.parse(new URL(plugin).pathname).name to extract a plugin "name" for deduplication. When a plugin's file:// URL resolves to dist/index.js (which is the standard convention for npm packages), the extracted name is just "index".
This means any two plugins whose main/entry point is dist/index.js are considered duplicates, and only the last one survives deduplication. This is a silent data loss — no warning or error is emitted.
Reproduction
Configure 3+ plugins in opencode.json where at least 2 resolve to dist/index.js:
{
"plugin": [
"@nick-vi/opencode-type-inject",
"opencode-supermemory",
"@tarquinen/opencode-dcp",
"oh-my-openagent"
]
}
Install all plugins in ~/.config/opencode/node_modules/ so that Stage 1 resolution (import.meta.resolve / createRequire) succeeds for all of them.
Result: Only type-inject (unique entry: .opencode/plugin/type-inject.ts) and oh-my-openagent (last index entry) are loaded. opencode-supermemory and @tarquinen/opencode-dcp are silently dropped.
Expected: All 4 plugins load.
Root Cause
In the config reader (src/config), Stage 1 resolution converts bare specifiers to file:// URLs:
data3.plugin[i8] = import.meta.resolve(plugin, options4.path);
// e.g. "file:///.../@tarquinen/opencode-dcp/dist/index.js"
Then deduplicatePlugins extracts names:
function getPluginName(plugin) {
if (plugin.startsWith("file://")) {
return path.parse(new URL(plugin).pathname).name;
// Returns "index" for any plugin with dist/index.js entry
}
// ...
}
Since most npm packages use dist/index.js as their entry point, this effectively limits users to one locally-installed plugin with a standard entry point.
Suggested Fix
For file:// URLs, extract the package name from the path instead of the filename:
function getPluginName(plugin) {
if (plugin.startsWith("file://")) {
const pathname = new URL(plugin).pathname;
// Extract package name from node_modules path
const nmIdx = pathname.lastIndexOf("/node_modules/");
if (nmIdx !== -1) {
const afterNm = pathname.substring(nmIdx + "/node_modules/".length);
// Handle scoped packages (@scope/name)
const parts = afterNm.split("/");
return parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
}
return path.parse(pathname).name; // fallback
}
// ...
}
Workaround
Don't install plugins with dist/index.js entry points into ~/.config/opencode/node_modules/. Let Stage 1 resolution fail so they remain as bare specifiers (which use the correct package name for deduplication) and get loaded via BunProc.install from ~/.cache/opencode/.
Environment
- opencode version: 1.3.3 (Homebrew, macOS ARM64)
- OS: macOS (Apple Silicon)
- Affected plugins: any plugin with
main: "dist/index.js" or "./dist/index.js" in package.json
Description
deduplicatePluginsin the config module usespath.parse(new URL(plugin).pathname).nameto extract a plugin "name" for deduplication. When a plugin'sfile://URL resolves todist/index.js(which is the standard convention for npm packages), the extracted name is just"index".This means any two plugins whose
main/entry point isdist/index.jsare considered duplicates, and only the last one survives deduplication. This is a silent data loss — no warning or error is emitted.Reproduction
Configure 3+ plugins in
opencode.jsonwhere at least 2 resolve todist/index.js:{ "plugin": [ "@nick-vi/opencode-type-inject", "opencode-supermemory", "@tarquinen/opencode-dcp", "oh-my-openagent" ] }Install all plugins in
~/.config/opencode/node_modules/so that Stage 1 resolution (import.meta.resolve/createRequire) succeeds for all of them.Result: Only
type-inject(unique entry:.opencode/plugin/type-inject.ts) andoh-my-openagent(lastindexentry) are loaded.opencode-supermemoryand@tarquinen/opencode-dcpare silently dropped.Expected: All 4 plugins load.
Root Cause
In the config reader (
src/config), Stage 1 resolution converts bare specifiers tofile://URLs:Then
deduplicatePluginsextracts names:Since most npm packages use
dist/index.jsas their entry point, this effectively limits users to one locally-installed plugin with a standard entry point.Suggested Fix
For
file://URLs, extract the package name from the path instead of the filename:Workaround
Don't install plugins with
dist/index.jsentry points into~/.config/opencode/node_modules/. Let Stage 1 resolution fail so they remain as bare specifiers (which use the correct package name for deduplication) and get loaded viaBunProc.installfrom~/.cache/opencode/.Environment
main: "dist/index.js"or"./dist/index.js"in package.json