anomalyco / anomalyco/opencode

[Desktop] npm-package plugins silently fail to load — plugin entry resolves to a directory under Node, works fine in the CLI

Open
#48,115 1 comment 0 reactions 1 assignee View on GitHub

@Brendonovich is already working on this.

Since Sep 9, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description

[Desktop] npm-package plugins silently fail to load — plugin entry resolves to a directory under Node, works fine in the CLI

Environment

OpenCode Desktop 1.18.30 (Electron 42.3.3 / Chrome 148.0.7778.218)
Desktop sidecar runtime Node v24.15.0 inside an Electron utilityProcess (NodeService) — typeof Bun === "undefined"
OpenCode CLI 1.18.30 (Bun-compiled binary)
OS macOS, darwin arm64
Plugin used for the report @zilliz/memsearch-opencode@0.3.15 ("main": "index.ts", "exports": "./index.ts", "type": "module")

Summary

On Desktop, every plugin installed as an npm package fails to load silently.
The same plugin loads and works correctly in the CLI.

The bundled plugin loader resolves the plugin entry point like this:

var resolveEntryPoint = (name, dir) => {
  let entrypoint;
  try {
    entrypoint = typeof Bun !== "undefined"
      ? import.meta.resolve(name, dir)   // Bun: resolves the package → .../index.ts  ✅
      : import.meta.resolve(dir);        // Node: resolves the *directory* itself      ❌
  } catch {
    entrypoint = void 0;
  }
  return { directory: dir, entrypoint };
};

On the Desktop the sidecar runs on Node, so the second branch is taken and entrypoint
becomes the package directory (file:///.../node_modules/@zilliz/memsearch-opencode).
ESM then rejects it:

ERR_UNSUPPORTED_DIR_IMPORT  Directory import '...' is not supported resolving ES modules

The failure is then discarded here:

const entrypoint = path.isAbsolute(ref.package)
  ? pathToFileURL(ref.package).href
  : (yield* npm.add(ref.package)).entrypoint;
if (!entrypoint) return;
const mod = yield* Effect.promise(() => import(entrypoint));   // ← throws here
const value = yield* Schema.decodeUnknownEffect(PluginModule)(mod).default;
...
}).pipe(Effect.ignoreCause);   // ← nothing is ever logged

Effect.ignoreCause swallows the error completely, so the app gives zero indication
not even at --log-level DEBUG — that a configured plugin was skipped. From the user's point of
view the plugin is simply "not working".

Minimal reproduction (independent of any plugin)

cat > /tmp/repro.mjs <<'EOF'
const dir = process.env.HOME +
  "/.cache/opencode/packages/@zilliz/memsearch-opencode/node_modules/@zilliz/memsearch-opencode";
console.log("import.meta.resolve(dir) =>", import.meta.resolve(dir));
try { await import(import.meta.resolve(dir)); }
catch (e) { console.log(e.code, "|", e.message.split("\n")[0]); }
EOF
node /tmp/repro.mjs

Actual output:

import.meta.resolve(dir) => file:///Users/zl/.cache/opencode/packages/@zilliz/memsearch-opencode/node_modules/@zilliz/memsearch-opencode
ERR_UNSUPPORTED_DIR_IMPORT | Directory import '/Users/zl/.cache/.../memsearch-opencode' is not supported resolving ES modules imported from '/tmp/repro.mjs'

Under Bun the equivalent import.meta.resolve(name, dir) returns
file:///.../node_modules/@zilliz/memsearch-opencode/index.ts and the import succeeds —
which is why the CLI is unaffected.

Second layer: .ts under node_modules

Even when the entry file is handed to the loader directly (see workaround below), Node refuses to
type-strip TypeScript that lives inside node_modules:

$ node -e 'import(".../node_modules/@zilliz/memsearch-opencode/index.ts")' --input-type=module
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING | Stripping types is currently unsupported for
files under node_modules, for "file:///Users/zl/.cache/.../index.ts"

Real-world impact

@zilliz/memsearch-opencode (a memory plugin registering memory_search, memory_get,
memory_transcript plus a capture side-process) was configured in opencode.json:

"plugin": ["@tarquinen/opencode-dcp@latest", "context-mode@latest", "@zilliz/memsearch-opencode"]
  • CLI: all three tools are registered (verified via
    curl http://127.0.0.1:<port>/experimental/tool/ids) and the plugin's side effects run.
  • Desktop: none of the tools exist, the plugin's side effects never start, and
    no error appears anywhere — server log, main.log, and --log-level DEBUG are all clean.
    grep -c memsearch in the logs only matches the user's own shell commands, never a plugin
    load event or a failure.

This makes npm plugins on Desktop effectively undistinguishable from "not installed".

Why this affects more than one plugin

Any plugin distributed as an npm package whose entry is TypeScript is hit by the same two
limitations on Desktop. Only plugins that exist as plain files under the
{plugin,plugins}/*.ts convention keep working, because that path goes through
pathToFileURL(file) instead of npm.add(...) / resolveEntryPoint.

Suggested fix

  1. Resolve the real entry file under Node, e.g. honour the package's exports/main:

    const { createRequire } = await import("node:module");
    const req = createRequire(pathToFileURL(path.join(dir, "package.json")));
    entrypoint = pathToFileURL(req.resolve(name)).href;
    
  2. Do not swallow plugin load failures. Even Effect.logWarning("failed to load plugin", { pkg, cause }) would turn a multi-hour "why is my plugin not working" session into a
    one-line answer.

  3. Note that after (1), Node will still refuse type stripping for .ts under node_modules
    (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so npm-distributed TypeScript plugins either
    need a compiled JS entry in the package, or the Desktop sidecar needs a registered TS loader.

Workaround (verified working)

Copy the plugin out of node_modules and reference it by absolute path (absolute paths
bypass npm.add / resolveEntryPoint entirely and go through pathToFileURL(file)):

mkdir -p ~/.config/opencode/plugins/memsearch
cp -R ~/.cache/opencode/packages/@zilliz/memsearch-opencode/node_modules/@zilliz/memsearch-opencode/ \
      ~/.config/opencode/plugins/memsearch/
// ~/.config/opencode/opencode.json
"plugin": [
  "/Users/zl/.config/opencode/plugins/memsearch/index.ts"
]

After a restart the plugin loads on Desktop again (verified: plugin init runs, registered side
processes start, and previously missing tool registration/side effects come back).

Caveat: the copy is not managed by npm any more, so plugin updates must be re-copied manually.

Plugins

zilliztech/memsearch

OpenCode version

1.18.30

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.