anomalyco / anomalyco/opencode
plugins: config-entry options silently ignored when the spec duplicates an auto-discovered local plugin
@Hona is already working on this.
Since Sep 12, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 2m
- Merged PRs (30d)
- 384
Description
Summary
A plugins config entry that targets a local plugin package also auto-discovered from the global plugins directory registers a second instance with the same plugin id when the package spec uses forward slashes on Windows. The second instance fails with Duplicate plugin ID: <id>, and its options are silently ignored — the plugin runs with defaults and nothing tells the user the options were dropped.
Environment
- opencode version: 2.0.2
- OS: Windows 11 (10.0.26220.9343, win32 x64)
- Terminal: Unavailable: reproduced against the background service via
opencode api(no TUI attached) - Shell: cmd.exe
- Install/channel: latest (2.0.2)
- Active plugins: 4 local entries auto-discovered from
~/.config/opencode/plugins/; nopluginskey in the global config at rest (the test entry was removed after the repro)
Reproduction
-
Create a local plugin package at
~/.config/opencode/plugins/example/whoseindex.tsdefault-exportsPlugin.define({ id: "example", setup(ctx) { /* register a tool that returns JSON.stringify(ctx.options) */ } }). -
Add a global config entry using an absolute path with forward slashes:
{ "plugins": [ { "package": "C:/Users/<user>/.config/opencode/plugins/example", "options": { "foo": "bar" } } ] } -
Let the config reload, then inspect the plugin list —
opencode api get /api/plugin:- two entries with
id: "example": the firststatus: "active", the secondstatus: "failed"with"error": "Duplicate plugin ID: example"; - the running plugin reports
ctx.options === {}— the configured options never arrive.
- two entries with
-
Change the spec to the same directory with native separators (
C:\Users\<user>\.config\opencode\plugins\example) or to a config-relative path ("./plugins/example"): the list shows exactly one active entry, andctx.optionsis{ "foo": "bar" }.
Expected Behavior
At least one of:
- path specs are canonicalized before duplicate detection, so the config entry dedupes against the auto-discovered package and its
optionsare applied; or - the entry's
optionsare merged into the already-loaded instance; or - at minimum, the dropped options are surfaced as a warning.
Actual Behavior
The config entry produces a rejected duplicate (Duplicate plugin ID: example) while the option-less auto-discovered instance keeps running. Options are silently dropped; the only trace is a failed entry in /api/plugin, and the server log shows only msg="loading plugin" lines with no warning.
Additional Context
- Verified repeatedly in both directions: forward-slash absolute path → duplicate + options ignored; backslash absolute or config-relative → single instance + options applied.
- Related: #7427 fixed duplicate loading in V1 via canonical-name dedupe; the V2 behavior above shows spec canonicalization still misses Windows separator styles.
- Impact: forward-slash absolute paths are a common cross-platform spelling, so users can silently lose plugin options — the plugin still works, just without its configuration, which is hard to notice.
- Workaround: write absolute specs with native separators, or use a path relative to the config file when the package sits beside it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.