Skip to content

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

@wowuliao11

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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions