Skip to content

Bug: getPluginName returns 'index' for file:// resolved plugins, causing deduplicatePlugins to silently drop unrelated plugins #17716

@yufeikang

Description

@yufeikang

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

  1. Configure two npm plugins in opencode.jsonc whose package entry points are named index.js/index.ts:
    {
      "plugin": [
        "oh-my-opencode",
        "some-other-plugin"
      ]
    }
  2. Ensure both are installed in the config directory's node_modules/
  3. 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)

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