anomalyco / anomalyco/opencode
Desktop app: local plugins load and register but their hooks (tool.execute.before, event) are never invoked
@Brendonovich is already working on this.
Since Jul 24, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
The Desktop app (ai.opencode.desktop) discovers and registers local plugins from ~/.config/opencode/plugins/ — the plugin shows up correctly in the old-layout status popover's Plugins tab with an active (green) status indicator. However, its hooks (tool.execute.before and event) are never actually invoked at runtime, even across multiple real tool calls that should trigger them. The identical, unmodified plugin file works correctly when run via the CLI (opencode run).
This looks like a registration/listing vs. hook-dispatch gap specific to the Desktop app's embedded server.
Related but distinct issues, for context (none of these appear to be duplicates — happy to be corrected):
- #34553 reports Desktop plugin behavior differing from CLI, but specifically for cwd/project-identity resolution, shell-env-var access, and
__require/import.meta.requiremodule loading — not hooks failing to fire at all. - #25748 (closed, PATH/shell-subprocess spawning for MCP servers) wouldn't explain this either, since the repro below uses
client.app.log(), which doesn't spawn a shell or depend onPATH. - #7006 (open) reports one specific hook (
permission.ask) missing itstrigger()call site after a refactor, with a pending fix (#19453) — a narrower, single-hook, CLI-and-Desktop-equal bug, whereas this report is about all hooks being silent, specifically in Desktop, despite successful plugin load/registration. - #33455 (closed, not_planned) and #37099 (open) both point at Desktop's plugin-loading pipeline having other fragility (config-array plugins not loading at all; stale plugin entries persisting in the UI after removal) — raised here in case the root cause turns out to be shared (Desktop's loader marking a plugin "loaded" for UI purposes without actually wiring it into the real dispatch table used by
trigger()).
Environment
- OpenCode Desktop: v1.18.4 (macOS, arm64)
- OpenCode CLI (for comparison): v1.17.11 (same machine)
- Plugin location:
~/.config/opencode/plugins/my-plugin.ts(also reproduced with a project-local equivalent)
Minimal repro plugin
import type { Plugin } from "@opencode-ai/plugin"
export const DebugPlugin: Plugin = async ({ $, client }) => {
await $`echo "plugin loaded at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> /tmp/debug-plugin-loaded.log`.nothrow()
return {
"tool.execute.before": async (input, output) => {
await client.app.log({
body: { service: "debug-plugin", level: "info", message: "tool.execute.before fired", extra: { tool: input.tool } },
})
},
event: async ({ event }) => {
if (event.type !== "permission.asked") return
await client.app.log({
body: { service: "debug-plugin", level: "info", message: "permission.asked fired" },
})
},
}
}
Steps to reproduce
- Place the plugin above in
~/.config/opencode/plugins/debug-plugin.ts. - Fully quit and relaunch OpenCode Desktop.
- Confirm the plugin is registered: open the status popover from the session header and check the Plugins tab (reproduced on both the old and new/V2 layout). The plugin is listed with a green/active status dot as
file:///.../debug-plugin.ts. - Trigger any tool call that requires permission approval (e.g. an MCP tool with
"permission": {"ask": ...}configured, or anyedit/bashcall gated toask). - Approve or reject the permission prompt as normal.
- Check
/tmp/debug-plugin-loaded.logand the app log (~/.local/share/opencode/log/opencode.log) fordebug-pluginentries.
Expected behavior
/tmp/debug-plugin-loaded.logcontains a line from plugin initialization.- The app log contains
tool.execute.before firedfor the tool call andpermission.asked firedfor the permission prompt.
Actual behavior
- Neither file/log ever receives an entry, across every real tool call and permission prompt triggered through the Desktop session — despite the plugin being listed as actively loaded in the Plugins tab.
- Running the exact same plugin file via
opencode run --dangerously-skip-permissions "..."(CLI) does produce both the marker file and thetool.execute.before/eventlog entries correctly, on the same machine, same config directory, same plugin file.
Additional notes
- This was discovered while building a plugin to surface real tool arguments in MCP permission prompts (workaround for #19549) — the workaround is fully proven to work via CLI but is completely inert in Desktop because its hooks never fire.
- Since even a shell-free signal (
client.app.log(), no$/spawn involved) never appears, this doesn't look like an Electron sandboxing/shell-restriction issue specifically — it looks like the Desktop app's plugin loader registers the module (enough to list it in the UI) but doesn't wire its returned hook object into the actual event/tool-execution dispatch path.
OpenCode version
Desktop v1.18.4 / CLI v1.17.11
Plugins
Yes — ~/.config/opencode/plugins/debug-plugin.ts (see repro above). Confirmed listed/active in Desktop's Plugins tab, confirmed working via CLI, confirmed inert via Desktop's actual hook dispatch.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.