Skip to content

V1 plugins cannot be made to load on V2 — no migration path for plugins that observe runtime activity #53706

Closed as duplicate

Description

@XiaTian-AC

Summary

OpenCode V2 ships two mutually incompatible plugin contracts, and a V1 plugin cannot be made to load on V2 by wrapping its export shape. There is no migration path for plugins that observe runtime activity (tool executions, chat messages, compaction) or register custom tools.

Verified against OpenCode v2.0.22 on Windows.

The break

V1 plugins are an async factory returning a hooks object:

export default async ({ client }) => ({
  "tool.execute.after": async (input, output) => { /* ... */ },
})

V2 validates the module with Schema.Struct and accepts only two shapes:

// packages/core/src/config/plugin/external.ts
const PluginModule = Schema.Struct({
  default: Schema.Union([
    Schema.Struct({ id: Schema.String, effect: Schema.declare(...) }),
    Schema.Struct({ id: Schema.String, setup: Schema.declare(...) }),
  ]),
})

A V1 module fails to load with:

Plugin must export a default definition with an id and an effect or setup function.
(cause: SchemaError(Expected object at ["def...

Wrapping the export does not help

The migration guide is explicit that the APIs are not translated into each other:

Keep each implementation on its own API; sharing an export does not translate V1 hooks into V2 hooks.

So a shim that adds id + setup() around the V1 factory loads successfully and then silently records nothing. That is worse than the current hard failure: the user believes memory/context capture is running when no hook is ever invoked.

The gap is in the V2 plugin API surface, not the loader

The V2 PluginContext does have equivalents for every V1 hook — this part is fine. What is missing is a way for a V1-shaped plugin to reach them without a rewrite:

V1 hook V2 equivalent available in v2.0.22
tool.execute.after ctx.tool.hook("execute.after") yes
chat.message ctx.session.hook("prompt") yes
experimental.chat.system.transform ctx.session.hook("context") yes
experimental.session.compacting ctx.session.hook("compaction") yes
event ctx.event.subscribe() yes
tool (custom tool map) ctx.tool.transform() yes

The problem is purely the entry contract: a plugin that only knows V1 cannot register any of the above, because V1 offers no ctx handle to register with. Every affected plugin must be rewritten hook by hook.

Measured ctx keys on v2.0.22:

agent, aisdk, app, command, event, experimental, generate, integration,
location, mcp, model, options, permission, plugin, provider, reference,
rpc, session, shell, skill, storage, tool, vcs, websearch, worktree

Impact

Plugins whose entire purpose is observing runtime activity cannot run on V2 at all. Concretely blocked on v2.0.22:

  • claude-mem (thedotmack/claude-mem) — persistent agent memory. Fails to load; --ide opencode install is unusable. Its MCP tools still work (registered separately), so search works while capture silently does not.
  • Any plugin built on experimental.chat.system.transform to inject context into the system prompt.
  • Any plugin registering custom tools via the V1 tool map.

This follows the V2 release; the migration guide mentions V1 support but only for the { id, server, setup } dual shape, which requires the plugin author to do the rewrite first.

Suggestions

  1. A compatibility path in the loader. When the default export is a function (the V1 shape), call it with a V2-compatible context whose tool.hook / session.hook / event.subscribe / tool.transform are present, and translate the returned V1 hooks object into registrations. This is a contained change in external.ts and would unblock every affected plugin at once.

  2. A first-class V1 export. Accept Schema.Struct({ server: Schema.declare(...) }) (no id, no setup) as a documented third union member, warning that only V1 hook names are honored. Cheaper than (1), but each plugin still needs its V1 hook names honored and gets no V2 capability.

  3. Actionable error message. When the default export is a function, the load error currently reads as if the shape were simply wrong. Naming the V1->V2 rewrite (and linking the migration guide) would save the debugging time I spent here.

Reproduction

// ~/.config/opencode/plugins/v1plugin.js
export default async () => ({ "tool.execute.after": async () => {} })
$ opencode run "hi"
Server plugin error
~/.config/opencode/plugins/v1plugin.js
Plugin must export a default definition with an id and an effect or setup function.

Related

Activity

  1. opencode-agent commented on Oct 7, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed write-up and the hook mapping table.

    This looks like the same request as #48138: a compatibility layer in the V2 loader so V1-style plugins (a default-exported factory returning a hooks object) load, with their hooks mapped onto ctx.tool.hook / ctx.session.hook / ctx.event.subscribe, plus a clearer error that points to the migration path. Please add your hook mapping (chat.message, experimental.chat.system.transform, experimental.session.compacting, and the custom tool map) and the claude-mem example there, so everything is tracked in one place.

    The bot will close this issue as a duplicate of #48138 in 1 day unless you reply explaining how your problem is different.

  2. github-actions commented on Oct 7, 2026

    @github-actions
    Contributor

    This issue doesn’t fully meet our contributing guidelines.

    What needs to be fixed:

    • This is a feature request, but it does not use the Feature Request template: add the required verification checkbox and start the title with [FEATURE]:.
    • Please provide the system details behind “Windows” (for example, the Windows version/build).

    Please edit this issue to address the above within 72 hours, or it will be automatically closed.


    This issue might be a duplicate of existing issues. Please check:

    • #48138: requests backward compatibility for V1 plugins in V2.
    • #48365: reports V1 plugins are unloadable with no migration path, among other V2 plugin gaps.
    • #39345: reports V1 plugin exports failing with SchemaError after upgrading to V2.
    • #50590: reports V2 rejecting a function default export at the plugin loader.

    If you believe this was flagged incorrectly, please let a maintainer know.

  3. opencode-agent commented on Oct 8, 2026

    @opencode-agent
    Contributor

    Closing this as a duplicate of #48138, since there hasn't been a reply in the last day. That issue asks for the same thing: a compatibility layer so V1-style plugins load on V2 with their hooks mapped onto V2 ctx registrations, plus a clearer load error. Please add your hook mapping (chat.message, experimental.chat.system.transform, experimental.session.compacting, the custom tool map) and the claude-mem example to #48138. If your case is actually different, reply here and explain how, and we can reopen. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

needs:complianceThis means the issue will auto-close after 2 hours.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions