Repository navigation
Description
Summary
On Windows, if the first import of a newly created plugin file fails transiently (the config-watcher-triggered load races the file write), the failure is cached for the rest of the process lifetime: every later load of that plugin — including after the file is fixed, and regardless of the ?mtime= cache-buster query — fails with ResolveMessage: Cannot find module '...?mtime=...'. Only restarting the service clears it. A plugin that hits this once stays failed in opencode2 plugin list forever, with an error that makes it look like the file is missing even when it imports fine from a fresh process.
Environment
- opencode version: 0.0.0-beta-18600
- OS: Windows_NT 10.0.26200 (win32 x64)
- Terminal: Unavailable: server-side issue
- Shell: Unavailable: background service loads the plugin
- Install/channel: beta (
opencode2, shared background service) - Active plugins: global plugins dir
~/.config/opencode/plugins/(auto-discovered); reproduced with a minimal file that has a valid V2 default-export object
Reproduction
- Run the opencode service, then create a new file
~/.config/opencode/plugins/probe.ts(valid V2 plugin:export default { id: "probe", async setup() {} }) via an editor or script that writes atomically (temp file + rename), which is what triggered it here. - The watcher fires while the file is still settling; the server logs:
msg="loading plugin" id="...\plugins\probe.ts" entrypoint="C:/Users/.../plugins/probe.ts?mtime=1787978240100"
message="failed to load plugin" target="...\plugins\probe.ts" cause="Cause([Die(ResolveMessage: Cannot find module 'C:/Users/.../probe.ts?mtime=1787978240100' imported from B/~BUN/root/chunk-kwq4rck1.js)])"
- The file now exists and is valid. Touch it (new mtime), edit it, reload config — every attempt fails identically, including with a brand-new
?mtime=value. opencode2 plugin listshows the plugin asfailedindefinitely.opencode2 service restart→ the exact same file loads fine.
The underlying behavior reproduces in plain Bun 1.4.0 on Windows without opencode, which suggests the loader just inherits it:
const p = "C:/Users/me/AppData/Local/Temp/t3.ts"
try { await import(p + "?mtime=1") } catch {} // fails: file does not exist yet
fs.writeFileSync(p, "export default 42")
await import(p + "?mtime=1") // STILL FAILS
await import(p + "?mtime=2") // STILL FAILS (new cache-buster)
await import(p) // STILL FAILS (no query)
// only a fresh process imports it successfullySo Bun caches the failed resolution per file path for the process lifetime; the ?mtime= suffix busts the module/transpiler cache but not the cached resolution failure.
Expected Behavior
- The first load of a newly created plugin file should not race the write that triggered the watcher (or should retry).
- Once the file exists, subsequent loads (certainly after an mtime change) should resolve it.
- A transient ENOENT-style failure should not permanently poison that plugin path for the process.
Actual Behavior
- First import races file creation → transient
ResolveMessage - Every subsequent import of that path fails instantly until the service restarts, regardless of
?mtime=value - Plugin status is stuck
failed; the reported "Cannot find module" is misleading (the file exists and imports fine from a fresh process)
Additional Context
- A second, smaller papercut from the same event: a plugin whose default export is a function (V1 shape) fails schema validation with
SchemaError(Expected object at ["default"]), but if the resolution poisoning happens first, the ResolveMessage masks the shape error entirely — diagnosing which problem you have requires guessing. - Timing evidence from the incident: file created at 04:37:20.100, first import attempt 04:37:20.818 (0.7 s later), 8 concurrent activation attempts all failed; a fresh process imported the identical
path?mtimespecifier successfully. - Reproducible: the poisoning reproduced deterministically in isolated Bun; the opencode-level race is intermittent per file creation but was hit on the first new plugin file created today.
Activity
github-actions commentedon Aug 29, 2026
This issue might be related to an existing issue. Please check:
ResolveMessage: Cannot find module '...?mtime=...'error on Windows with local .ts plugins. That issue covers the compiled binary failing to resolve?mtime=specifiers at all; your report is distinct (the resolution failure is cached after a transient race), but they share the same error surface and Windows-only scope — worth cross-referencing to avoid duplicate fix efforts.MilkyWay008 commentedon Aug 29, 2026
Classic Bun negative-cache, it memoizes the failed module lookup per path so your
?mtime=buster does nothing, the ENOENT is what got cached not the file. The minimal Bun 1.4.0 repro you built is the gold here, that's a genuine upstream bug worth filing, restart of the service is just the only clean eviction in the meantime. Oh also, btw, I built a portable Hermes OTG and I run it on USB as my rescue agent; super handy doing quick diagnose & fix, check it, imo it'll help you. https://github.com/MilkyWay008/Hermes-OTGMonNa-free commentedon Sep 29, 2026
Another instance of the same "failure cached for process lifetime" behavior, but at a different stage — so it likely won't be fixed by the Bun import-cache fix alone:
serve --servicebackground processskill.transformwithSchemaError: Missing key at ["path"]→disabled plugin after transform failureGET /api/pluginkept returning the pre-fix failure (status: failedwith the stalereffrom before the fix). Closing/reopening the TUI had no effect since the CLI just reconnects to the same serve process.POST /api/plugin/checkreturns the list unchanged;POST /api/plugin/updatewith validtargetsreturns empty, state unchanged;/api/plugin/reload,/api/plugin/refresh,/api/plugin/enable→ 404opencode serve --serviceprocess and restarting clears itSo the process-lifetime caching applies to post-import validation failures too, not just Bun's negative module cache. An explicit reload/retry route (as requested in #44389 → #39987) would cover both cases.
(Filed this separately as #52061 before finding this thread — closing it as a duplicate and keeping the details here.)
opencodejr commentedon Oct 28, 2026
Thanks for the very thorough report and the minimal Bun snippet.
I tried it in a Linux sandbox with Bun 1.4.2, the version current opencode embeds. The first import of the missing file failed with the same
ResolveMessage: Cannot find module '...?mtime=1'. Once the file existed, all three later imports (?mtime=1,?mtime=2and no query) loaded it fine. So on Linux, Bun doesn't keep the failed lookup, and the plugin loader recovers.That points to Windows-specific Bun behaviour, and I can't test Windows here, so this needs a maintainer with a Windows machine to confirm. One thing that would help: does your plain-Bun snippet still fail with a recent Bun (1.4.2 or newer)? Until this is looked at,
opencode2 service restartis the workaround.Related: #57658 (a Windows plugin kept failing with ENOENT on an existing file until a service restart) was closed as a duplicate of this issue.