Skip to content

v2.0.18: plugin ctx.provider.transform callbacks never execute — plugins cannot register providers (official docker image) #51802

Closed as not planned

Description

@skiffek

Description

On the official docker image ghcr.io/anomalyco/opencode:2.0.18, in-process plugins load and their setup runs to completion, but ctx.provider.transform(cb) never executes cb — not immediately after await, and not when the registry is later rebuilt by a read. No provider or model registered by a plugin ever materializes. No error, warning, or log line is produced at any point.

The plugin authoring example from https://opencode.ai/v2/docs/build/plugins/ fails identically, so this is not specific to my plugin.

Environment

  • opencode version: 2.0.18 (image ghcr.io/anomalyco/opencode:2.0.18, linux/arm64; :latest is 1.18.33 so 2.0.18 is the newest v2 image)
  • Plugin loading route: global discovery dir ~/.config/opencode/plugins/*.ts (only route that loads local plugins in this build — see additional findings)
  • Runner: background service mode (opencode serve --service, then opencode models)

Reproduction

  1. Start a container with a scratch home and a project dir:
docker run --rm --entrypoint sh ghcr.io/anomalyco/opencode:2.0.18
export HOME=/tmp/h XDG_CONFIG_HOME=/tmp/h/.config XDG_DATA_HOME=/tmp/h/.local/share \
       XDG_CACHE_HOME=/tmp/h/.cache XDG_STATE_HOME=/tmp/h/.local/state CI=true NO_COLOR=1
mkdir -p "$XDG_CONFIG_HOME/opencode/plugins" /tmp/proj && cd /tmp/proj
printf '{}\n' > "$XDG_CONFIG_HOME/opencode/opencode.json"
  1. Create $XDG_CONFIG_HOME/opencode/plugins/docs.ts — the docs example, with the SDK imported via an absolute path (see finding 3 for why):
import { Model, Provider } from "/work/node_modules/@opencode/plugin/dist/promise/index.js"

export default {
  id: "docs.example",
  async setup(ctx) {
    const providerID = Provider.ID.make("acme")
    const models = [{ ...Model.Info.default(providerID, Model.ID.make("reasoner")), name: "Acme Reasoner" }]
    await ctx.provider.transform((editor) => {
      editor.add({
        info: {
          ...Provider.Info.empty(providerID),
          name: "Acme",
          activation: "enabled",           // also reproduced with "auto"
          package: "@opencode/ai/providers/openai-compatible",
          settings: { baseURL: "http://127.0.0.1:8000/v1" },
        },
        models,
      })
    })
  },
}

(/work = any dir containing node_modules/@opencode/plugin; mount one or bun install it.)

  1. Run the service and list models:
opencode serve --service --log-level debug --print-logs > /tmp/serve.log 2>&1 &
sleep 8
opencode models

Expected Behavior

acme/reasoner appears in opencode models (per the docs' own plugin example: after await ctx.provider.transform(...) registers the source, reads reflect it; "the next read rebuilds it by replaying every active transform").

Actual Behavior

  • opencode models lists only the built-in opencode/* free models. acme never appears, no matter how long you wait or how many times you re-run.
  • serve.log shows loading plugin id=.../plugins/docs.ts entrypoint=file://... and no failure, and nothing else about the plugin.
  • Instrumentation proof that the callback never runs: registering a transform whose callback sets a flag, awaiting the returned thenable (it resolves), then calling await ctx.provider.list() — the flag is still unset and the list does not contain the provider. The registration is functionally a no-op.

Additional findings from the same investigation (same image)

  1. Plugin setup/effect exceptions are silently swallowed. A setup that throws immediately produces no log line at any --log-level. The only way a plugin can surface diagnostics is triggering an unhandled rejection (e.g. setTimeout(() => { throw new Error(...) })). This made the transform no-op very hard to see.
  2. Config-declared local plugin paths are inert. plugins: ["/work", "file:///work", "/work/src/index.ts"] in both global and project opencode.json never produce a loading plugin line, even though opencode debug config shows the document. opencode plugin add /work rejects local paths ("must be an npm registry package or Git package specifier"). Only the discovery dir (~/.config/opencode/plugins/*.ts) actually loads local plugins. This contradicts https://opencode.ai/v2/docs/plugins/ which documents all of these forms.
  3. No SDK import map for plugins. import ... from "@opencode/plugin" inside a discovery-dir plugin fails with Cannot find package '@opencode/plugin'. Plugins only work if they happen to sit somewhere with a resolvable node_modules. Since the docs' examples all import @opencode/plugin, every copy-pasted example fails to load out of the box.
  4. ctx.storage.get/set are synchronous at runtime while the SDK's dist/promise types declare Promise (docs also show await ctx.storage.set(...)). A promise-conforming plugin crashes with storage.get(key).then is not a function — silently, per finding 1. (My plugin now wraps every call in Promise.resolve().then(...) to tolerate both.)
  5. Related existing report: providers: config-declared providers never materialize into registry (v2.0.15) #51285 (config-declared providers never materialize on 2.0.15) — same "declared source never reaches the registry" family, different input path.

Impact

No third-party v2 plugin can register providers/models on the current v2 image, and the failure is completely invisible to plugin authors (no error output anywhere). Combined with finding 2/3, the documented "build a plugin" workflow cannot produce a working provider plugin on this build.

Happy to provide the full probe transcripts (serve.log captures, instrumentation shims) if useful.

Activity

  1. github-actions commented on Sep 28, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of or related to existing reports. Please check:

    • #51703: Plugin-contributed providers/models are missing from v2.0.18 catalog reads during initialization; that report describes a transient startup window, while this issue reports transforms never executing even after later reads.
    • #51285: Config-declared providers never materialize in the registry, a similar symptom through a different source path.
    • #42856: User-defined local provider models do not enter the v2 catalog (configuration-only).
  2. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed write-up. I tried to reproduce this on @opencode/cli@2.0.18 (Linux x64, installed with bun, not the docker arm64 image), following your steps: scratch HOME/XDG dirs, an empty opencode.json, and the docs example in ~/.config/opencode/plugins/docs.ts with the SDK imported by absolute path. I added file logging to the plugin, then ran opencode serve --service --log-level debug --print-logs followed by opencode models.

    For me the transform does run:

    • The plugin log showed setup start → transform awaited → transform callback ran. ctx.provider.list() right after the await already included acme.
    • opencode models listed acme/reasoner alongside the opencode/* free models.
    • The callback runs on the next read, not during the await. That matches the docs ("the next read rebuilds it by replaying every active transform").

    A few other things I saw:

    • A plugin whose setup throws was logged for me as WARN "failed to load plugin" ... Die(Error: ...) in the serve log, and ctx.storage.get(...) returned a promise on 2.0.18.
    • The first opencode models right after starting opencode serve --service by hand sometimes printed nothing at all. Later runs were fine. The hand-started service also once failed with Failed to start server. Is port 49374 in use?. It looks like it can race the background service the CLI starts on its own.

    Things that would help:

    • Does the problem still happen if you skip the manual serve --service and just run opencode models (or opencode models --standalone) a couple of times?
    • Can you share the full serve log from a failing run, and the output of opencode plugin list if available?
    • Does it only happen in the ghcr.io/anomalyco/opencode:2.0.18 arm64 image? If so, the exact docker run command (mounts, user) would help.

    The config-declared local plugin paths (your finding 2) are a separate question. I couldn't check them cleanly in this run.

  3. opencodejr commented on Oct 31, 2026

    @opencodejr

    I'm closing this for now since there hasn't been a reply. On @opencode/cli@2.0.18 (Linux x64) I couldn't reproduce the main problem. The plugin's ctx.provider.transform callback ran on the next read, ctx.provider.list() included acme, and opencode models listed acme/reasoner. A setup that throws was also logged as failed to load plugin.

    If you still see this, please comment here with:

    • the full serve log from a failing run
    • whether running opencode models (or opencode models --standalone) without starting serve --service by hand still fails
    • the exact docker run command, if it only happens in the arm64 image

    We can reopen it then. The config-declared local plugin paths are a separate question and may be worth their own issue. Thanks for the detailed report.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions