Skip to content

Bug: deduplicatePlugins silently removes plugins whose entry point is dist/index.js #20203

Description

@wowuliao11

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

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions