Skip to content

[BUG] Custom providers fail on Desktop (Electron) with ProviderInitError — ESM directory import bug in resolveEntryPoint #31909

Description

@holytshirt

Bug Report: Custom providers fail on Desktop (Electron) with ProviderInitError due to ESM directory import

Description

Custom npm providers that work perfectly on the CLI (Bun) fail immediately on OpenCode Desktop (Electron/Node.js) with ProviderInitError. The error is caused by a one-argument vs two-argument difference in resolveEntryPoint between the Bun and Node code paths.

Environment

  • OpenCode Desktop v1.17.3 (Electron 42.3.3, Node 24.15.0)
  • macOS arm64
  • Custom provider: @my/opencode-ai-gateway-stuff (ESM package with "type": "module")

Root Cause

In packages/opencode/src/provider/provider.ts, the resolveEntryPoint function has an asymmetry between Bun and Node:

var resolveEntryPoint = (name38, dir2) => {
  let entrypoint;
  try {
    const resolved = typeof Bun !== "undefined" 
      ? import.meta.resolve(name38, dir2)  // ✅ Bun: resolves package entry point correctly
      : import.meta.resolve(dir2);          // ❌ Node: just returns the directory as a URL
    entrypoint = exports_Option.some(resolved);
  } catch {
    entrypoint = exports_Option.none();
  }
  return { directory: dir2, entrypoint };
};

Bun path (import.meta.resolve(name, dir)): Correctly resolves the package name relative to the directory, returning the actual entry file (e.g., file:///.../<pkg>/dist/index.js).

Node path (import.meta.resolve(dir)): Simply converts the directory path to a file:// URL without resolving the package entry point. Returns file:///.../<pkg-dir> (a directory URL).

Later in resolveSDK:

const importSpec = installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href;
const mod = await import(importSpec);  // 💥 ERR_UNSUPPORTED_DIR_IMPORT

Node.js ESM does not support import() of a directory URL — it always fails with ERR_UNSUPPORTED_DIR_IMPORT, regardless of package.json main/exports fields or the presence of index.js.

Proposed Fix

Change the Node path to use two-argument import.meta.resolve (supported since Node 20.6+):

var resolveEntryPoint = (name38, dir2) => {
  let entrypoint;
  try {
    const resolved = typeof Bun !== "undefined"
      ? import.meta.resolve(name38, dir2)
      : import.meta.resolve(name38, dir2);  // Fix: use two args for Node too
    entrypoint = exports_Option.some(resolved);
  } catch {
    entrypoint = exports_Option.none();
  }
  return { directory: dir2, entrypoint };
};

Or simply:

const resolved = import.meta.resolve(name38, dir2);

Both Bun and Node 20.6+ support the two-argument form. Since the Desktop app bundles Node 24.15.0, this is safe.

Steps to Reproduce

  1. Create a custom provider package with "type": "module" and an exports field pointing to ./dist/index.js
  2. Configure it in opencode.json:
    {
      "provider": {
        "my-provider": {
          "npm": "my-custom-provider-package",
          "options": { "baseURL": "https://example.com" },
          "models": { "my-model": { "name": "My Model" } }
        }
      }
    }
  3. Use the CLI → works ✅
  4. Use the Desktop app → ProviderInitError ❌

Debug Evidence

From server logs (opencode.log):

level=ERROR run=37105c8f message=process error=ProviderInitError
  stack="ProviderInitError\n    at resolveSDK (file:///Applications/OpenCode.app/Contents/Resources/app.asar/out/main/chunks/node-BcDYwDH9.js:470695:15)\n    at async exports_promise.refineRejection.ModelNotFoundError2.modelID (file:///Applications/OpenCode.app/Contents/Resources/app.asar/out/main/chunks/node-BcDYwDH9.js:470723:21)"

The error repeats on every retry — the provider never initializes.

Verified by reproducing the exact Desktop flow in Node:

import { pathToFileURL } from 'url';
const dir = '~/.cache/opencode/packages/<pkg>/node_modules/<pkg>';
const importSpec = pathToFileURL(dir).href;
await import(importSpec);  // ERR_UNSUPPORTED_DIR_IMPORT

Additional Context

Workaround

Until this is fixed, custom provider packages can work around the issue by having users run a script that replaces the package directory with a shim file:

PKG=~/.cache/opencode/packages/<pkg>/node_modules/<scope>/<name>
mv "$PKG" "${PKG}__pkg"
echo 'export { myFactory } from "./<name>__pkg/dist/index.js";' > "$PKG"

Related Issues

Activity

  1. hereswilson commented on Jun 12, 2026

    @hereswilson

    I just noticed this issue was already assigned. I should have checked that before opening the PR, and I’ll make sure to do that in the future.

    Sorry for the miss. Happy to defer to the assigned maintainer and close the PR if it conflicts with planned work. If useful, the PR is tested and can also serve as a reference implementation.

  2. holytshirt commented on Jun 12, 2026

    @holytshirt
    Author

    Hi @hereswilson I think it's okay as @StarpTech was automatically assigned
    Thanks for the PR. This is causing a lot of issues for our users of the OpenCode Desktop would be good for them not to have to run the patch.

  3. holytshirt commented on Jul 3, 2026

    @holytshirt
    Author

    Update — the affected code has moved, but the bug is still present on dev.

    Since this was filed, provider.ts was refactored: resolveEntryPoint is no longer in packages/opencode/src/provider/provider.ts. Provider now resolves entrypoints via Npm.add(...) and reads item.entrypoint. The resolution logic itself was relocated to packages/core/src/npm.ts.

    The bug came along with it unchanged. Current dev, packages/core/src/npm.ts:53:

    entrypoint = typeof Bun !== "undefined" ? import.meta.resolve(name, dir) : import.meta.resolve(dir)

    Same asymmetry as originally reported: the Bun branch uses the two-arg import.meta.resolve(name, dir), while the Node branch uses the one-arg import.meta.resolve(dir), which resolves to the package directory URL and throws ERR_UNSUPPORTED_DIR_IMPORT under Node/Electron. The caller (Npm.add) passes the package directory in as dir, so the broken path is still reached and the original repro still applies on Desktop.

    The proposed fix still holds — it just needs to target the new file. Using the two-arg form on both paths (import.meta.resolve(name, dir)) resolves the package export correctly on both Bun and Node 20.6+.

    Note that #32060 ("fix(core): resolve npm provider entrypoints", Closes #31909) covers this but is still open / unmerged, so anyone hitting this on dev should look at packages/core/src/npm.ts rather than the old provider.ts path.

  4. 0genlab commented on Aug 24, 2026

    @0genlab

    The community fix #32060 was auto-closed by the stale-PR cleanup before review, and the affected code has since moved to packages/core/src/npm.ts (as @holytshirt noted above). I've opened #44722 — a fresh implementation against the current layout: on Node, resolve the entry from the installed manifest (exports → module → main → index.js, with a createRequire fallback) instead of importing the package directory. Verified in real Node 22: the old path reproduces ERR_UNSUPPORTED_DIR_IMPORT, the new path loads and initializes the provider.

    Until a fix lands, a cache-independent workaround for Desktop users: point the provider's npm field at the installed entry file via a file:// URL (OpenCode passes those straight to import()), e.g. install the provider package anywhere with npm and use file:///abs/path/node_modules/<pkg>/dist/index.mjs.

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