Repository navigation
Description
Description
packages/plugin/src/host.ts is intended to probe several conventional plugin entrypoints and continue when a subpath is missing. Under Bun 1.3.14, Bun.resolveSync throws a ResolveMessage for a missing module or package export. That object has a whitelisted code on its prototype, but it does not extend Error:
constructor: ResolveMessage
error instanceof Error: false
"code" in error: true
error.code: ERR_MODULE_NOT_FOUND
own properties: []
At source revision 9128e847bdbf9e5a58b7bdc33896e484e745aedd, packages/plugin/src/host.ts:25-38 first requires error instanceof Error. The first missing conventional subpath is therefore rethrown instead of being skipped.
Two real targets reproduce different points in the probe sequence:
local directory with index.ts but no server.ts:
Cannot find module '<plugin>/server'
[email protected], which exports ./server and ./tui but not ./rpc:
Cannot find module 'opencode-usage-stat/rpc'
The TUI plugin panel showed these targets as failed, while the server log shows both server entrypoints loading normally. Whether the packaged server's embedded Bun represents ResolveMessage in exactly the same way is uncertain; the deterministic reproduction and fix below use the source/dev TUI on system Bun 1.3.14, connected to the beta service.
A local fix replaced the instanceof Error gate with a string code check against the existing whitelist. After that change, both targets resolve as expected:
local: { server: "file://.../index.ts", tui: undefined, rpc: undefined }
usage-stat: { server: "file://.../dist/server.js", tui: "file://.../dist/tui.jsx", rpc: undefined }
Suggested fix in packages/plugin/src/host.ts:25-38: read (error as { code?: unknown }).code, require it to be a string, and apply the current missing-path whitelist without requiring error instanceof Error.
Plugins
- Local
opencode-enhanced-subagentdirectory (index.ts, noserver.ts) [email protected](exports.,./server, and./tui, but no./rpc)
OpenCode version
TUI source at 9128e847bdbf9e5a58b7bdc33896e484e745aedd, connected to opencode2 v0.0.0-beta-19271; Bun 1.3.14.
Steps to reproduce
- With Bun 1.3.14, call
Bun.resolveSync("<local-plugin>/server", "<local-plugin>")for a plugin directory that hasindex.tsbut noserver.ts. - Observe a
ResolveMessagewithcode === "ERR_MODULE_NOT_FOUND"andinstanceof Error === false. - Call the current
Host.resolve({ directory: "<local-plugin>" })implementation. It throws on/serverinstead of falling back to/index. - Alternatively, call
Host.resolvefor an installed package that exports server/TUI entrypoints but no./rpc; it resolves the first two and then throws on the missing RPC export. - Replace the
instanceof Errorgate with a typed string-code check and repeat. Both calls return completeEntrypointsobjects without throwing.
Expected: missing optional/conventional subpaths whose error code is in the existing whitelist are skipped, regardless of whether Bun's thrown object extends Error.
Actual: every Bun ResolveMessage fails the instanceof Error check and becomes fatal.
Screenshot and/or share link
No screenshot. The exact probe output is included above.
Operating System
WSL2 Ubuntu, Linux 6.18.33.2-microsoft-standard-WSL2 x86_64.
Terminal
TERM=xterm-256color, shell /bin/bash.
Local verification notes
- Reproduced both failing targets with the exact pre-fix predicate from
packages/plugin/src/host.ts. - Applied and tested the narrow predicate change locally in commit
977afc6c74948533addda46f6653e9511146451f. - Re-ran both real-target probes successfully after the change.
- Ran
bun test test/host.test.tsfrompackages/plugin: 9 passed, 0 failed. - The claim that
c72b535deepredates this path is not correct: that revision'spackages/tui/src/plugin/context.tsx:607already callsHost.resolve(target).tui. The guard itself was introduced byed3259a9b7beforec72b535dee.
Activity
DDwsgood commentedon Sep 9, 2026
Related reports found while checking for duplicates (different mechanisms, listed for triage):
.tsplugins fail to load on Windows (older report, possibly overlapping with tui: local plugins silently fail to load on Windows since beta-18721 upgrade (cli.json migration regression) #46408).Note: the
instanceof Errorgate analyzed here predates both of those reports (introduced byed3259a9b7), so this is likely an independent cause.alvaropas17 commentedon Sep 30, 2026
Same root cause, worse surface: a server-side plugin reload dies and registers zero agents
I hit the exact
Host.resolvegate described above, but on the server plugin-reload path instead of the TUI plugin panel. There the consequence is not "one target shown as failed" — the whole plugin generation dies, including builtins, so no agents are registered at all and the session fails withAgent not found <id>. Since it is the same predicate and the same suggested fix, I'm adding the evidence here rather than opening a duplicate.2.0.18, branchv2, HEAD5bcf6b7(built and run from source). Also reproduced on an installed binary dated 2026-09-27, and on a compiled binary.Summary
Starting a new server from source with a config that includes any directory plugin lacking a
server/indexentrypoint ends with 0 agents and the agent call failing withAgent not found <id>. The server logs the fatal plugin-generation failure:Reproduced with
<config>/plugins/quote-probe(contains onlytui.ts) and with<dir>/opencode2-telegram(noserverentrypoint).Impact
The failure is total, not partial: because the rethrown
ResolveMessageaborts the whole plugin generation, builtins are dropped too, so every agent disappears. The user-facing error (Agent not found <id>) gives no hint that a single optional subpath probe is the cause. This is the same symptom family reported in #48121 (Session.AgentNotFoundErrorafter a failed reload), but here it is deterministic and has a concrete trigger.Steps to reproduce
Configure a directory plugin that has no
server/indexentrypoint, e.g. a plugin dir containing onlytui.ts:Start a fresh server from source (e.g.
bun run … serve, private--standalone).Watch the server log:
failed to reload plugins,cause="Cause([Die(ResolveMessage: Cannot find module '<dir>\server' from '<dir>')])".Run any agent: it fails with
Agent not found <id>.Minimal, framework-free check of the underlying predicate (Bun):
That is exactly the object the gate rejects.
Root cause
packages/plugin/src/host.ts→Host.resolve()tolerates a failed probe only whenerror instanceof Erroranderror.codeis in{ ENOENT, ENOTDIR, MODULE_NOT_FOUND, ERR_MODULE_NOT_FOUND, ERR_PACKAGE_PATH_NOT_EXPORTED, ERR_UNSUPPORTED_DIR_IMPORT }; anything else is rethrown.In the Bun runtime,
resolveModule(packages/util/src/runtime/import.bun.ts) usesBun.resolveSync, which throws aResolveMessagewithinstanceof Error === falseandcode === "ERR_MODULE_NOT_FOUND". Because it does not pass theinstanceof Errorcheck, it is rethrown instead of skipped.Effect.syncthen captures the throw asDie, killing the whole plugin generation (builtins included), which is why zero agents end up registered.Empirical verification of the mechanism (not inference):
Bun.resolveSync("<dir>/server", "<dir>")on a missing path throwsResolveMessagewithinstanceof Error === false; wrapping the same failure in a realErrorcarrying the samecodemakesHost.resolvetolerate it and return the present entrypoints.Minimal fix
Same as suggested in the issue above; either narrow change works:
Host.resolve, accept the missing-path condition based on a stringerror.codeeven wheninstanceof Erroris false (read(error as { code?: unknown }).code, require it to be a string, apply the existing whitelist).resolveModule(packages/util/src/runtime/import.bun.ts), rethrow a realErrorpreservingcode.Note: a compiled binary hits the same path (it uses the same Bun condition /
Bun.resolveSync), and the 2026-09-27 installed binary also returns 0 agents — so this is not source/dev-only.Workaround (temporary, not a fix)
Until the predicate is fixed, avoid giving the probe a missing entrypoint: ensure each configured directory plugin has a
serverorindexfile (a stubserver/index.tsis enough). ThenHost.resolvenever throws the fatalResolveMessage.How this was surfaced (context)
Found while validating an opt-in experiment,
experimental.direct_exec— letting theshelltool acceptexecutable+argvinstead of acommandstring, so it can spawn a process directly (no PowerShell hop on Windows). A fresh server built with that experiment active had a directory plugin without aserverentrypoint in config, which triggered the reload death above and masked the experiment for a while.Experiment files (all reverted on 2026-09-30, no PR):
packages/schema/src/config/experimental.ts,packages/core/src/config/normalize.ts,packages/core/src/shell.ts,packages/core/src/shell/parse.ts,packages/core/src/tool/plugin/shell.ts,packages/plugin/src/effect/shell.ts,packages/plugin/src/promise/shell.ts; tests/bench created and removed:packages/core/test/direct-exec-parity.test.ts,packages/core/test/fixture/direct-exec.ts,packages/core/test/perf-direct-exec.harness.test.ts.A/B validation (private
--standaloneserver from source, agentbuild, one run per path):bun.execommand="rg needle sample.txt ."bun.exe → powershell.exe → rg.exeneedle herecommand=null,executable=<abs rg.exe>,argv=["needle","sample.txt"]rg.exe(direct, no PowerShell)needle hereResult: PASS (exact input, no PowerShell in the tree, correct output). The saving is confined to the shell phase (225 → 62 ms, ~72 %); it does not show up in total turn time (n=1). Reported only as provenance for the plugin failure above — the
direct_execexperiment itself is reverted and out of scope here.feat(module): add ponytail opencode plugin
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report and the probe output. I could only reproduce this on the Bun version you used, not on the one the repo targets:
Bun.resolveSyncthrows aResolveMessagethat is notinstanceof Error.Host.resolveon a local plugin withindex.tsbut noserver.tsthen throwsCannot find module '<dir>/server', exactly as you describe. This is still true on the latestdev(d1bd20fa).package.json(packageManager: [email protected]),ResolveMessageis anError. The same call returns{ server: "file://.../index.ts", tui: undefined, rpc: undefined }without throwing.@opencode/[email protected]) has Bun 1.4.4 built in, so it isn't affected either.So this only shows up when the source TUI runs on a Bun older than the pinned version. Upgrading Bun (
bun upgrade) should fix it for your dev setup. If you still see plugins marked as failed with Bun 1.4.4, or with a releasedopencodebinary, please reply with the version and the plugin setup. Your suggested check (any thrown value with a whitelisted stringcode) would still be a reasonable hardening for maintainers to consider.Related plugin-loading reports, which look like different causes: #58647, #49608, #50434, #46095.
opencodejr commentedon Oct 31, 2026
Closing this as not planned since we haven't heard back. In our testing,
Host.resolveonly failed when the source TUI ran on Bun 1.3.14. That version throws aResolveMessagethat isn't anError. On Bun 1.4.4, which the repo pins, and in the released@opencode/clibinary (it has Bun 1.4.4 built in), missing/serverand/rpcsubpaths are skipped as intended. Upgrading Bun should fix your dev setup.If plugins still show as failed with Bun 1.4.4 or a released
opencodebinary, please comment with the version and plugin setup and we can reopen this. Thanks again for the detailed report.