Repository navigation
Description
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(imageghcr.io/anomalyco/opencode:2.0.18, linux/arm64;:latestis 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, thenopencode models)
Reproduction
- 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"- 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.)
- Run the service and list models:
opencode serve --service --log-level debug --print-logs > /tmp/serve.log 2>&1 &
sleep 8
opencode modelsExpected 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 modelslists only the built-inopencode/*free models.acmenever 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 callingawait 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)
- Plugin
setup/effectexceptions are silently swallowed. Asetupthat 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. - Config-declared local plugin paths are inert.
plugins: ["/work", "file:///work", "/work/src/index.ts"]in both global and projectopencode.jsonnever produce aloading pluginline, even thoughopencode debug configshows the document.opencode plugin add /workrejects 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. - No SDK import map for plugins.
import ... from "@opencode/plugin"inside a discovery-dir plugin fails withCannot find package '@opencode/plugin'. Plugins only work if they happen to sit somewhere with a resolvablenode_modules. Since the docs' examples all import@opencode/plugin, every copy-pasted example fails to load out of the box. ctx.storage.get/setare synchronous at runtime while the SDK'sdist/promisetypes declarePromise(docs also showawait ctx.storage.set(...)). A promise-conforming plugin crashes withstorage.get(key).then is not a function— silently, per finding 1. (My plugin now wraps every call inPromise.resolve().then(...)to tolerate both.)- Related existing report: providers: config-declared providers never materialize into registry (v2.0.15) #51285 (config-declared
providersnever 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
github-actions commentedon Sep 28, 2026
This issue might be a duplicate of or related to existing reports. Please check:
opencodejr commentedon Oct 28, 2026
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 emptyopencode.json, and the docs example in~/.config/opencode/plugins/docs.tswith the SDK imported by absolute path. I added file logging to the plugin, then ranopencode serve --service --log-level debug --print-logsfollowed byopencode models.For me the transform does run:
setup start→transform awaited→transform callback ran.ctx.provider.list()right after theawaitalready includedacme.opencode modelslistedacme/reasoneralongside theopencode/*free models.await. That matches the docs ("the next read rebuilds it by replaying every active transform").A few other things I saw:
setupthrows was logged for me asWARN "failed to load plugin" ... Die(Error: ...)in the serve log, andctx.storage.get(...)returned a promise on 2.0.18.opencode modelsright after startingopencode serve --serviceby hand sometimes printed nothing at all. Later runs were fine. The hand-started service also once failed withFailed 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:
serve --serviceand just runopencode models(oropencode models --standalone) a couple of times?opencode plugin listif available?ghcr.io/anomalyco/opencode:2.0.18arm64 image? If so, the exactdocker runcommand (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.
opencodejr commentedon Oct 31, 2026
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'sctx.provider.transformcallback ran on the next read,ctx.provider.list()includedacme, andopencode modelslistedacme/reasoner. Asetupthat throws was also logged asfailed to load plugin.If you still see this, please comment here with:
opencode models(oropencode models --standalone) without startingserve --serviceby hand still failsdocker runcommand, if it only happens in the arm64 imageWe 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.