v1 file-plugin loader never falls back to legacy named exports when a v2 default export is present
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 86/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- tooling
Research direction
Start in packages/opencode/src/plugin/shared.ts at readV1Plugin, then trace applyPlugin and getLegacyPlugins in packages/opencode/src/plugin/index.ts. Run the relevant plugin loader tests and add coverage for a dual-shape module. Done means detect mode allows the named v1 hook to load while strict v1 handling still reports invalid defaults.
Written by the indexing model from the issue text.
Description
v1 file-plugin loader never falls back to legacy named exports when a v2 default export is present
Version: 1.18.31 (desktop AppImage; same core as CLI)
Area: packages/opencode/src/plugin/{index,shared}.ts — external file-plugin loading
What happens
A file plugin that carries both contracts — a named hook-map export (v1) and a default { id, setup } export (v2), like the official dcg-guard.js generated by dcg install --opencode — is silently skipped on the v1 runtime. Its tool.execute.before hook never fires. For a security guard this means protection is down with zero visible signal in the app (the failure is only an Effect log line).
Repro (live A/B, same hook function)
~/.config/opencode/plugins/dcg-guard.js(official, dual-shape):git reset --hardin a scratch repo executed (block missed).~/.config/opencode/plugins/dcg-v1-shim.jscontaining onlyexport const server = DcgGuardre-exported from the same file (same function reference, no default export): identicalgit reset --hardaborted with dcg's block message, worktree intact.
The only difference between the two cases is the module shape, so the loader — not the hook — is at fault.
Root cause
applyPlugin (plugin/index.ts) calls readV1Plugin(mod, spec, "server", "detect") first. With a default export of { id, setup } (has id, no server), readV1Plugin (plugin/shared.ts) does not bail out in detect mode — it falls through to:
if (kind === "server" && server === undefined) {
throw new TypeError(`Plugin ${spec} must default export an object with server()`)
}
The throw rejects applyPlugin, the caller logs failed to load plugin and continues, and the legacy path (getLegacyPlugins, which would have picked up the named DcgGuard export) is never reached — it sits behind the if (plugin) branch that threw.
Notably the docs say a plugin "exports one or more plugin functions" and named-only modules (no default export) do load fine through the legacy path — so dual-shape modules are strictly worse than v1-only ones, which contradicts the documented contract.
Proposed patch
In detect mode, a non-matching default export should mean "not a v1 plugin", not an error — let the legacy path try:
--- a/packages/opencode/src/plugin/shared.ts
+++ b/packages/opencode/src/plugin/shared.ts
@@ readV1Plugin
if (kind === "server" && server === undefined) {
+ if (mode === "detect") return
throw new TypeError(`Plugin ${spec} must default export an object with server()`)
}
if (kind === "tui" && tui === undefined) {
+ if (mode === "detect") return
throw new TypeError(`Plugin ${spec} must default export an object with tui()`)
}
Strict mode (explicit v1 plugins) keeps throwing; only auto-detection falls back. Happy to PR this + a loader test if the approach looks right.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 20m
- Merged PRs (30d)
- 358
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.
More from anomalyco/opencode
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
[FEATURE]: Bump gitlab-ai-provider to 6.15.1 on v2 (Fable 5.1, GPT-6 Astra, workflow reconnect fix) Open
Difficulty 1/5 Under an hour Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
All issues in anomalyco/opencode
Similar issues
-
Browser Waiting for: Product Owner
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
getsentry/sentry-javascript#24577 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
agilepathway/label-checker#640 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
copse-dev/agent-pane#2953 ·
-
agentic-workflows
Difficulty 1/5 Under an hour Newbie friendliness 85/100
githubnext/rig#534 ·
-
automation missing-model model-sync provider:pioneer
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
anomalyco/models.dev#7701 ·