Repository navigation
Description
Description
On the Bun runtime (compiled binary), editing a local plugin file while the shared service is running never picks up the change. The reload path in packages/plugin/src/source.bun.ts invalidates Bun's module cache with delete require.cache[file] keyed by the entrypoint (symlink) path, but Bun keys the module record by the resolved real path. When the plugin lives under a symlinked config directory (e.g. ~/.config/opencode → a dotfiles checkout), the deletion is a no-op and import(entrypoint) keeps returning the stale module for the life of the process.
Environment
- opencode version: 2.0.20 (embeds Bun 1.4.2)
- OS: Linux 6.8.0-139-generic (linux x64)
- Terminal: TERM_PROGRAM=ghostty, TERM=xterm-256color, COLORTERM=truecolor
- Shell: /bin/bash
- Install/channel: latest (homebrew
opencode-v2) - Active plugins:
@plannotator/opencode@latest(npm), plus an auto-discovered local file plugin at~/.config/opencode/plugins/herdr-agent-state.js(the herdr terminal-integration plugin)
Reproduction
- Make
~/.config/opencodea symlink to a real directory that contains apackage.json(no"type"field) andplugins/herdr-agent-state.jswhose only export is a named function (no default export) — this mirrors herdr integration v10. - Start the shared service. The plugin load fails as expected for the old shape:
msg="loading plugin" id=~/.config/opencode/plugins/herdr-agent-state.js entrypoint=file:///…/herdr-agent-state.js message="failed to load plugin" target=~/.config/opencode/plugins/herdr-agent-state.js cause="Cause([Fail(PluginModule.LoadError: Plugin must export a default definition with an id and an effect or setup function. (cause: SchemaError(Missing key at [\"default\"])))])" - Fix the file in place: add
export default { id: "herdr.opencode", server, setup() {} }(mirrors herdr integration v13, the dual V1/V2 shape from the plugin migration guide). - The watcher fires and the plugin reloads — but every reload fails with the exact same
SchemaError(Missing key at ["default"]), even though the file on disk now has a valid default export. Onlyopencode service restartclears it (a fresh process loads the fixed file fine — verified with a sandboxedopencode serve).
Minimal isolated repro against Bun 1.4.2, matching what prepareSource's visit() does:
const mod1 = await import(pathToFileURL(symlinkPath).href) // keys: ["HerdrAgentStatePlugin"] (old file, no default)
// … update the real file on disk to add the default export …
delete require.cache[symlinkPath] // no-op: Bun keys the cache by the real path
const mod2 = await import(pathToFileURL(symlinkPath).href) // still ["HerdrAgentStatePlugin"] — stale module
delete require.cache[realPath] // this is what actually invalidates it
const mod3 = await import(pathToFileURL(symlinkPath).href) // fresh: ["HerdrAgentStatePlugin", "default"]Expected Behavior
Changing a local plugin file while the service runs should re-evaluate it on reload. The cache invalidation should work under a symlinked config directory, like it does under a real path — e.g. resolve the real path before deleting from require.cache, or freshen the import URL on reload the way source.node.ts does with ?__opencode_reload=<version>.
Actual Behavior
Every reload after the edit returns the stale cached module, so the plugin keeps failing with:
PluginModule.LoadError: Plugin must export a default definition with an id and an effect or setup function.
(cause: SchemaError(Missing key at ["default"]))
until the service is restarted. Related symptom of the same symlinked config dir: recurring watcher errors in the server log,
message="failed to subscribe" directory=~/.config/opencode
cause="Error: inotify_add_watch on '~/.config/opencode' failed: Not a directory"
(the watcher falls back to the node backend, so it is noisy rather than fatal).
Additional Context
packages/plugin/src/source.bun.ts—visit()deletesrequire.cache[file]using the path derived from the entrypoint URL (the symlink path). Bun 1.4.2 keys the module cache by the resolved real path, so the deletion misses andHost.load(entrypoint)returns the cached old module.packages/plugin/src/source.node.tsavoids the class of problem by freshening the import specifier with?__opencode_reload=<version>on each reload.- Fresh process → correct load (verified); same process after in-place edit → stale module (verified). 100% reproducible.
- Workaround:
opencode service restartafter any edit to a local plugin file that lives under a symlinked config directory.
Activity
github-actions commentedon Sep 30, 2026
Thanks for updating the issue with the Bug Report description and reproduction details.
jp41011 commentedon Sep 30, 2026
This does seem related to a previous issue: #51602
But it looks like that one was automatically closed.
Main issue content was by the report skill built into opencode.
I'll leave that in case its useful.
Additional information from the human:
I noticed this issue because i had setup the herdr plugin for opencode while i was using opencode v1.
I then upgraded to opencode v2. I ran the opencode v1 to v2 migration
But I think at the time herdr did not have a version that was compatible with opeoncode v2.
I just ignored the popup message with the herdr plugin error message.
Until today when i finally had enough energy to update some skills and plugins.
I updated herdr. Their site says it now supports opencode v2.
I closed and reopened opencode. Thinking that restart would be enough.
but I still got the herdr plugin error message on startup.
I forgot about the switch to the opencode service, in opencode v2, running in the background and i technically did not restart that.
So maybe this can be attributed to the dumb human behind the keyboard.
Or maybe a reminder can be shown to the user reminding them they need to restart the opencode service and not just the client instance. I don't know. Documenting it here in case its useful to someone else in the future.
p.s. I do have a dotfiles folder that I symlink to my ~/.config/opencode/ directory using gnu stow. I don't think that was the main issue. But maybe it didn't help with the caching.
Thank you.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report and the minimal repro. I could reproduce this.
On 2.0.20: I made
~/.config/opencodea symlink to a real directory containingpackage.jsonandplugins/herdr-agent-state.js, with only a named export in the plugin. The service failed to load the plugin, as expected for that file. I then addedexport default { id: "herdr.opencode", setup() {} }in place. The watcher triggered a reload, and it failed again with the sameSchemaError(Missing key at ["default"]).On the latest
dev(86ce19d), run from source: the script below edits the plugin and reads it again. It shows the same thing:This matches your analysis:
source.bun.tsclears the module cache using the symlink path, not the resolved real path. Until it's addressed, restarting the service after editing the plugin (or pointing the config at the real directory) works around it.Reproduction script (reload-symlink.ts)
Run it:
bun reload-symlink.ts