Skip to content

beta: V1 plugins unloadable with no migration path, custom commands unreachable headless, no V2 plugin/API docs for agents #48365

Open

Description

@OpenJawCode

Summary

Beta-19271 hardening pass surfaced four independent defects plus a docs gap:
(1) all V1-style plugins fail to load under the V2 schema with no migration
path; (2) custom slash commands registered via command.transform cannot be
invoked through headless run (treated as plain text); (3) recall-scoring
footgun for context-hook authors (behavior note); (4) no V2 plugin API
migration guide or agent-oriented API docs exist, which is why (1)–(3) cost a
full hardening pass to diagnose instead of ten minutes with docs.

Environment

  • opencode version: 0.0.0-beta-19271
  • OS: Linux 6.17.0-1009-oracle (linux arm64)
  • Terminal: Unavailable: headless service (systemd)
  • Shell: /bin/bash
  • Install/channel: beta
  • Active plugins: oh-my-opencode-slim, conclave-v2 (local path),
    magic-context-v2 (local path). Notes: a temporary redacted wire-logging
    plugin was attached during diagnosis and removed; opencode-supabase and
    list failures below reproduce from project-local config.

Reproduction

1. V1 plugins unloadable, no migration path

  1. Configure any of @op1/goals, opencode2-goal-plugin@1.0.5,
    @cortexkit/opencode-magic-context@0.41.2, opencode-supabase, or a
    bare list entry in opencode.jsonc plugins.
  2. Start the server.
  3. Every boot logs, e.g.:
    Plugin must export a default definition with an id and an effect or setup function (cause: SchemaError … ["default"]["effect"] … ["default"]["setup"])
    or must default export an object with server(), or
    Plugin export is not a function.

2. Custom commands unreachable headless

  1. Register a command via ctx.command.transform(editor => editor.add({name: "ctx-status", …})) (loads fine, no errors).
  2. Run opencode2 run --format json "/ctx-status".
  3. The model receives /ctx-status as plain user text ("Checking session
    context…") — the handler never fires. No CLI/API path exists to invoke a
    registered custom command outside the TUI palette.

3. Recall-scoring footgun (behavior note, local port)

recall(db, "<fixed generic query>", …) plus score-gated fallback
(hits.length>0 ? hits : pool) systematically excludes fresh, distinctive
observations whenever generic session-event rows match the fixed terms. A
planted unique marker (DB-verified, 3 rows from head) was never injected
while noise rows matched. Recency-first retrieval fixed it locally. Suggest
documenting retrieval guidance for context-hook authors.

4. Missing docs: V2 plugin migration guide + agent-oriented API reference

There is no published guide for migrating a V1 plugin (export default async (ctx) => … / {id, server} shapes) to the V2 Plugin.define +
effect/setup API, and no agent-oriented reference for the plugin
surface that coding agents actually need: full SessionHooks inventory
(prompt/context/model.request/http.request/http.response/retry) with
mutability and firing semantics per entry point (TUI vs run vs
exec vs subagent), the command.transform palette-vs-headless matrix,
and the session/tool/event domain method list. The only reason defects
(1)–(3) above required source-diving the bundled @opencode/plugin
.d.ts files is that this documentation does not exist. Request:
(a) a V1→V2 migration guide with before/after for each export shape,
(b) a hook/API reference written for agent implementers, not just humans
clicking the TUI.

Expected Behavior

  1. Either V1 export shapes keep working or the error names the exact migration
    (Plugin.define + effect/setup) with a link.
  2. A supported headless invocation path for registered custom commands
    (e.g. run recognizing leading-/ or an explicit flag).
  3. Guidance on recall/injection patterns that survive real session noise.
  4. Migration + API docs sufficient for an agent to port a plugin without
    reverse-engineering bundled type declarations.

Actual Behavior

  1. SchemaError spam every boot; entire plugin categories (goals, memory,
    supabase) dead on beta with no path forward.
  2. Commands exist only in the TUI palette; automation cannot reach them.
  3. Naive fixed-query recall silently drops fresh context.
  4. No migration guide; API surface discoverable only via bundled .d.ts.

Additional Context

Activity

  1. github-actions commented on Sep 10, 2026

    @github-actions
    Contributor

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

    If your issue has meaningful differences from the above (e.g. headless custom command invocation in item (2) or the recall-scoring note in item (3) are not covered there), it may be worth splitting those into separate focused issues and closing this one as a duplicate of the relevant existing reports.

  2. famewolf commented on Sep 24, 2026

    @famewolf

    Impact on a real installation (2026-09-23, v2.0.15 sidecar):

    5 of 8 plugins in a v1 config failed to load under v2: opencode-auto-resume, smartsnip-fork, lowfat.ts, openslimedit, opencode-handoff. All 8 load correctly on v1.18.31.

    The 2 that DID load (opencode-token-optimizer, @tarquinen/opencode-dcp) happened to already use the v2 object shape.

    Workaround we used: compiled TS→JS via bun build and restructured exports to match the v2 runtime schema. This is a manual, per-plugin fix with no tooling or migration path.

    The practical consequence: users who have built a plugin stack on v1 (including official plugins like DCP) are effectively stranded on v1. Upgrading to v2 silently breaks their setup with no warning beyond a WARN log line.

  3. mphanthj-bot commented on Oct 21, 2026

    @mphanthj-bot

    Adding a real-world data point for item (1) — a V1 plugin shipped by a first-party vendor — plus the exact error the loader produces for it.

    Reproduction

    Microsoft Intelligent Terminal 0.2.2572.0 deploys its OpenCode plugin (wt-agent-hooks) to ~/.config/opencode/plugins/wt-agent-hooks.js. It is a V1 plugin:

    export const WtAgentHooks = async ({ directory }) => ({
      "chat.message": async (input) => { /* ... */ },
      "tool.execute.before": async (input, output) => { /* ... */ },
      event: async ({ event }) => { /* ... */ },
      dispose: async () => { /* ... */ },
    })

    On opencode 2.1.2 (Windows 11 x64) the V2 loader rejects it on every boot:

    level=WARN message="failed to load plugin"
      target="C:\Users\<user>\.config\opencode\plugins\wt-agent-hooks.js"
      cause="Cause([Fail(PluginModule.LoadError: Plugin must export a default definition with an id and an effect or setup function. (cause: SchemaError(Missing key
      at [\"default\"])))])"
    

    GET /api/plugin confirms state.status: "failed". The vendor's app therefore never receives a single OpenCode lifecycle event, and the only user-visible symptom is that the terminal never shows OpenCode agent status.

    Why this variant is worth a targeted message

    This is the Missing key at ["default"] variant, not the Expected object at ["default"] variant from #50590: the module has no default export at all, so the error never mentions that the module looks like a V1 plugin, never names Plugin.define / setup(ctx), and never links a migration guide. A user who does not read the server log gets no signal whatsoever.

    Something along these lines would have turned a silent integration failure into a one-line fix:

    Plugin must export a default definition with an id and an effect or setup function.
    No default export found — this looks like a V1 plugin (a named export returning a hooks object).
    See https://opencode.ai/v2/docs/build/plugins/migrate-v1
    

    Gap in the migration guide's lookup table (item 4)

    Item (4) of this issue is now partly addressed: a V1→V2 plugin migration guide exists at https://opencode.ai/v2/docs/build/plugins/migrate-v1.

    Two things the port still needed are not covered by it, and neither is obvious from the type declarations:

    1. The event payload field moved. The guide maps V1 event → ctx.event.subscribe(), but V2 events carry their payload on event.data, while V1 used event.properties. The guide's example only reads event.type.

    2. Several V1 event types were renamed. The port needed these mappings, none of which appear in the guide:

      • session.updated → session.renamed / session.metadata.updated
      • session.error → session.execution.failed
      • question.asked / question.replied → form.created / form.replied / form.cancelled

      The session id also moved: V1 read properties.info.id, V2 reads data.sessionID.

    For reference, the working V2 shape that satisfied the loader was a plain object export with no SDK import at all:

    export default {
      id: "wt-agent-hooks",
      async setup(ctx) {
        await ctx.session.hook("prompt", (event) => { /* event.sessionID */ })
        await ctx.tool.hook("execute.before", (event) => { /* event.tool, event.input */ })
        const controller = new AbortController()
        for await (const ev of ctx.event.subscribe({ signal: controller.signal })) {
          /* ev.type, ev.data */
        }
        return () => controller.abort()
      },
    }

    Note the missing SDK import is deliberate — see #50434 for local plugins being unable to resolve @opencode/plugin.

  4. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed write-up. Here is how each part maps to existing work:

    1. V1 plugins not loading: V2 deliberately rejects V1 plugin exports, so V1 plugins have to be ported. Making old plugins load anyway (a compatibility layer, or an error that names the migration) is already requested in [FEATURE]: backward compatibility for v1 plugins in v2 #48138, so please add your use cases there.
    2. Custom commands unreachable from run: this is tracked in run: --command removed in v2 and slash commands are not expanded — no CLI path for headless command invocation #49903 (run --command was removed in v2 and a leading / isn't expanded in run).
    3. Recall scoring: this is about how a context hook chooses what to inject, so it's mostly guidance for plugin authors. Thanks for sharing what worked for you (retrieving the most recent entries first).
    4. Docs: a V1→V2 plugin migration guide now exists under Build → Plugins → Migrate plugins from V1, with before/after config and code. The server plugin docs also cover ctx.command.transform, list and reload. A reference written for agents, listing each hook with when it fires in TUI, run, exec and subagents, is still a fair request.

    Maintainers will look at the requests above. Related issues:

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions