Description
When two or more plugins are loaded via file:// paths and their JS filenames are identical (e.g. both named index.js), deduplicatePlugins() silently drops all but one. No error or warning is logged.
Reproduction
Expected: Both plugins load.
Actual: Only one loads (the later entry wins). The other is silently discarded.
Root Cause
getPluginName() extracts just the bare filename (without extension) for file:// URLs. Both paths resolve to canonical name "index". deduplicatePlugins() then treats them as the same plugin and keeps only the last one (reverse-order processing, last = highest priority).
Impact
This is a silent failure — no error, no warning, no log entry. The dropped plugin simply doesn't exist at runtime. This is especially likely during local plugin development where dist/index.js is the standard build output for most bundlers (bun, esbuild, rollup, etc.).
Suggested Fix
For file:// URLs, derive the canonical name from a more unique portion of the path rather than just the bare filename. Options:
- Use the parent directory name + filename (e.g.
plugin-a/index → "plugin-a")
- Use the full path as the canonical name for
file:// entries
- At minimum, log a warning when dedup discards a
file:// plugin
Workaround
Ensure each file:// plugin has a unique JS filename:
ln -sf /path/to/plugin/dist/index.js /path/to/plugin/dist/my-plugin.js
Environment
- OpenCode (latest)
- Linux x86_64
- Multiple
file:// plugins in config
Description
When two or more plugins are loaded via
file://paths and their JS filenames are identical (e.g. both namedindex.js),deduplicatePlugins()silently drops all but one. No error or warning is logged.Reproduction
Expected: Both plugins load.
Actual: Only one loads (the later entry wins). The other is silently discarded.
Root Cause
getPluginName()extracts just the bare filename (without extension) forfile://URLs. Both paths resolve to canonical name"index".deduplicatePlugins()then treats them as the same plugin and keeps only the last one (reverse-order processing, last = highest priority).Impact
This is a silent failure — no error, no warning, no log entry. The dropped plugin simply doesn't exist at runtime. This is especially likely during local plugin development where
dist/index.jsis the standard build output for most bundlers (bun, esbuild, rollup, etc.).Suggested Fix
For
file://URLs, derive the canonical name from a more unique portion of the path rather than just the bare filename. Options:plugin-a/index→"plugin-a")file://entriesfile://pluginWorkaround
Ensure each
file://plugin has a unique JS filename:{ "plugin": [ "file:///home/user/plugin-a/dist/plugin-a.js", "file:///home/user/plugin-b/dist/index.js" ] }Environment
file://plugins in config