anomalyco / anomalyco/opencode

Desktop app: local plugins load and register but their hooks (tool.execute.before, event) are never invoked

Open
#38,604 0 comments 0 reactions 1 assignee View on GitHub

@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.require module 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 on PATH.
  • #7006 (open) reports one specific hook (permission.ask) missing its trigger() 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
  1. Place the plugin above in ~/.config/opencode/plugins/debug-plugin.ts.
  2. Fully quit and relaunch OpenCode Desktop.
  3. 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.
  4. Trigger any tool call that requires permission approval (e.g. an MCP tool with "permission": {"ask": ...} configured, or any edit/bash call gated to ask).
  5. Approve or reject the permission prompt as normal.
  6. Check /tmp/debug-plugin-loaded.log and the app log (~/.local/share/opencode/log/opencode.log) for debug-plugin entries.
Expected behavior
  • /tmp/debug-plugin-loaded.log contains a line from plugin initialization.
  • The app log contains tool.execute.before fired for the tool call and permission.asked fired for 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 the tool.execute.before/event log 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.