Repository navigation
import.meta.resolve(dir) one-arg form returns directory URL on Node.js, causing custom npm providers to fail with ERR_UNSUPPORTED_DIR_IMPORT #46141
Description
Summary
Custom npm providers configured via the npm field in opencode.jsonc fail to initialize in OpenCode's TUI/server mode when running on Node.js, while the same configuration works in the CLI (Bun). The root cause is an asymmetry in import.meta.resolve() between Bun and Node.js in packages/core/src/npm.ts.
Environment
- OpenCode: v1.18.25
- Runtime: Node.js v24.14.0 (also affects Electron/Desktop)
- OS: Windows 10
Symptom
When a custom provider is configured via npm package name:
{
"plugin": ["opencode-cmd-provider"],
"provider": {
"commandcode": {
"npm": "opencode-cmd-provider",
"name": "Command Code",
"options": {
"apiKey": "..."
}
}
}
}opencode models commandcode— works ✅opencode run -m commandcode/deepseek/deepseek-v4-flash "hello"— works ✅- Interactive TUI — fails with
Failed to initialize provider: commandcode❌
Using file:// URL directly to the dist entry file fixes all modes:
"npm": "file:///absolute/path/to/opencode-cmd-provider/dist/index.js"Root Cause
In packages/core/src/npm.ts, the resolveEntryPoint function handles Bun and Node.js differently:
const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
let entrypoint: Option.Option<string>
try {
const resolved = typeof Bun !== "undefined"
? import.meta.resolve(name, dir) // ✅ Bun: resolves to entry file
: import.meta.resolve(dir) // ❌ Node: returns directory URL
entrypoint = Option.some(resolved)
} catch {
entrypoint = Option.none()
}
return { directory: dir, 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:///.../dist/index.js) - Node path:
import.meta.resolve(dir)— merely converts the directory path to afile://URL
The caller then does await import(importSpec) with the resolved path. Node.js ESM does not support import() of a directory URL — it throws ERR_UNSUPPORTED_DIR_IMPORT regardless of what package.json declares in main/exports.
This explains why:
- CLI commands (
run,models) work — they may use a cached/loaded SDK or a different resolution path - TUI/server fails — it freshly initializes providers via
Npm.add()and hits the directory import error file://works — it bypasses npm resolution entirely, passing the exact file path toimport()
Fix
Both Bun and Node.js 20.6+ support the two-argument form of import.meta.resolve(name, dir). The fix is to use it consistently:
const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
let entrypoint: Option.Option<string>
try {
- const resolved = typeof Bun !== "undefined"
- ? import.meta.resolve(name, dir)
- : import.meta.resolve(dir)
+ const resolved = import.meta.resolve(name, dir)
entrypoint = Option.some(resolved)
} catch {
entrypoint = Option.none()
}
return { directory: dir, entrypoint }
}Related
- PR fix(core): resolve npm provider entrypoint to a file on Node #44722 — Alternative approach that resolves entry from manifest (
exports→module→main→index.js) instead of fixing theimport.meta.resolvecall - PR fix(core): resolve npm provider entrypoints #32060 — Earlier attempt (auto-closed by stale bot)
- Issue [BUG] Custom providers fail on Desktop (Electron) with ProviderInitError — ESM directory import bug in resolveEntryPoint #31909 — Original bug report for Desktop/Electron
- Issue Plugin fails to initialize in OpenCode TUI/server mode when referenced by npm package name — works with
file://path rashidrazak/opencode-cmd-provider#102 — Downstream plugin report documenting thefile://workaround
Activity
github-actions commentedon Aug 29, 2026
This issue might be a duplicate of existing issues. Please check:
resolveEntryPointuses one-argimport.meta.resolve(dir)on Node.js, returning a directory URL that causesERR_UNSUPPORTED_DIR_IMPORT. Same proposed fix (use two-arg form consistently), same workaround (file://path).